From f5bd214439e783b891b68f0534c46bed6179e068 Mon Sep 17 00:00:00 2001 From: Uniswap Labs Service Account Date: Tue, 1 Oct 2024 19:48:08 +0000 Subject: [PATCH] ci(release): publish latest release --- CODEOWNERS | 1 + RELEASE | 66 +- VERSION | 2 +- apps/extension/jest-setup.js | 3 + apps/extension/package.json | 4 +- apps/extension/src/app/OnboardingApp.tsx | 22 +- .../src/app/features/accounts/AccountItem.tsx | 10 +- .../accounts/AccountSwitcherScreen.tsx | 6 +- apps/extension/src/app/features/dapp/store.ts | 38 +- .../dappRequests/DappRequestContent.tsx | 4 +- .../src/app/features/dappRequests/accounts.ts | 2 +- .../EthSend/Approve/ApproveRequestContent.tsx | 8 +- .../SignTypedDataRequestContent.tsx | 2 + .../src/app/features/dappRequests/utils.ts | 1 + .../src/app/features/home/HomeScreen.tsx | 3 + .../app/features/home/TokenBalanceList.tsx | 2 +- .../home/introCards/HomeIntroCardStack.tsx | 20 + .../features/onboarding/ClaimUnitagScreen.tsx | 60 + .../features/onboarding/OnboardingScreen.tsx | 2 +- .../onboarding/OnboardingStepsContext.tsx | 1 + .../onboarding/create/PasswordCreate.tsx | 13 +- .../features/onboarding/intro/IntroScreen.tsx | 10 +- .../onboarding/scan/ScanToOnboard.tsx | 2 +- .../send/SendFormScreen/SendFormScreen.tsx | 5 +- .../internal/EllipsisDropdown.tsx | 4 +- .../src/app/features/swap/SwapFlowScreen.tsx | 2 +- .../features/warnings/StorageWarningModal.tsx | 6 +- .../app/hooks/useOpeningKeyboardShortCut.ts | 1 + .../extension/src/app/navigation/constants.ts | 1 + apps/extension/src/app/navigation/utils.ts | 2 +- .../src/contentScript/WindowEthereumProxy.ts | 1 + apps/extension/src/manifest.json | 2 +- apps/mobile/android/app/build.gradle | 6 +- .../ios/Uniswap.xcodeproj/project.pbxproj | 48 +- .../ios/WidgetsCore/Utils/DataQueries.swift | 14 +- apps/mobile/jest-setup.js | 2 +- apps/mobile/package.json | 2 +- apps/mobile/src/app/migrations.ts | 2 +- apps/mobile/src/app/modals/AppModals.tsx | 5 - .../src/app/modals/BackupWarningModal.tsx | 8 +- .../src/app/modals/HiddenTokenInfoModal.tsx | 67 - apps/mobile/src/app/modals/SwapModal.tsx | 2 +- apps/mobile/src/app/navigation/NavBar.tsx | 82 +- .../__snapshots__/Text.test.tsx.snap | 4 +- .../RemoveWallet/useModalContent.tsx | 2 + .../RequestModal/KidSuperCheckinModal.tsx | 2 +- .../src/components/Requests/ScanSheet/util.ts | 5 +- .../src/components/Requests/Uwulink/utils.ts | 2 +- .../Requests/WalletConnectModals.tsx | 4 +- .../Settings/BiometricAuthWarningModal.tsx | 8 +- .../TokenBalanceList/TokenBalanceList.tsx | 60 +- .../TokenDetails/TokenDetailsHeader.tsx | 4 +- .../components/accounts/AccountList.test.tsx | 7 +- .../src/components/accounts/AccountList.tsx | 1 + .../__snapshots__/AccountHeader.test.tsx.snap | 10 +- .../components/explore/ExploreSections.tsx | 22 +- .../components/explore/FavoriteHeaderRow.tsx | 4 +- .../explore/FavoriteTokenCard.test.tsx | 15 +- .../components/explore/FavoriteTokenCard.tsx | 6 +- .../src/components/explore/TokenItem.tsx | 2 + .../FavoriteHeaderRow.test.tsx.snap | 21 +- .../FavoriteWalletCard.test.tsx.snap | 2 +- .../__snapshots__/RemoveButton.test.tsx.snap | 2 +- .../__snapshots__/TokenItem.test.tsx.snap | 22 +- .../explore/search/SearchEmptySection.tsx | 2 +- .../search/SearchPopularNFTCollections.tsx | 2 +- .../search/SearchPopularTokens.graphql | 2 +- .../explore/search/SearchPopularTokens.tsx | 3 +- .../explore/search/SearchResultsSection.tsx | 4 +- .../components/explore/search/utils.test.ts | 16 +- .../src/components/explore/search/utils.ts | 8 +- .../forceUpgrade/ForceUpgradeModal.tsx | 4 +- .../src/components/home/ActivityTab.tsx | 5 +- .../src/components/home/HomeExploreTab.tsx | 4 +- .../introCards/OnboardingIntroCardStack.tsx | 48 +- apps/mobile/src/components/icons/Favorite.tsx | 11 +- .../components/mnemonic/SeedPhraseDisplay.tsx | 12 +- .../contexts/LayoutContextProvider.tsx | 2 + .../__snapshots__/DecimalNumber.test.tsx.snap | 4 +- .../TextWithFuseMatches.test.tsx.snap | 30 +- .../src/components/tooltip/TooltipButton.tsx | 2 +- .../src/components/unitags/AvatarSelection.ts | 1 + .../src/components/unitags/UnitagBanner.tsx | 34 +- .../CloudBackupProcessingAnimation.tsx | 1 + apps/mobile/src/features/appRating/saga.ts | 2 +- .../fiatOnRamp/FiatOnRampAmountSection.tsx | 25 +- .../src/features/import/GenericImportForm.tsx | 8 +- .../import/InputWithSuffix.android.tsx | 4 +- .../features/import/InputWithSuffix.ios.tsx | 2 - .../GenericImportForm.test.tsx.snap | 8 +- .../mobile/src/features/modals/ModalsState.ts | 1 - apps/mobile/src/features/modals/modalSlice.ts | 6 - apps/mobile/src/features/nfts/item/traits.tsx | 2 +- .../onboarding/BackupSpeedBumpModal.tsx | 1 + .../src/features/openai/OpenAIContext.tsx | 3 +- .../src/features/send/SendFormScreen.tsx | 7 +- .../send/SendRecipientSelectFullScreen.tsx | 3 +- .../src/features/send/SendTokenForm.tsx | 4 +- .../unitags/ChooseProfilePictureScreen.tsx | 1 + .../unitags/EditUnitagProfileScreen.tsx | 4 +- .../src/features/walletConnect/selectors.ts | 8 +- .../src/features/walletConnect/utils.ts | 3 +- .../src/screens/ExternalProfileScreen.tsx | 2 +- apps/mobile/src/screens/FiatOnRampScreen.tsx | 11 +- apps/mobile/src/screens/HomeScreen.tsx | 2 +- .../screens/Import/OnDeviceRecoveryScreen.tsx | 8 +- .../RestoreCloudBackupLoadingScreen.tsx | 2 +- .../RestoreCloudBackupPasswordScreen.tsx | 1 + ...oreCloudBackupPasswordScreen.test.tsx.snap | 4 +- .../src/screens/NFTCollectionScreen.tsx | 2 +- .../screens/Onboarding/ManualBackupScreen.tsx | 6 +- .../__snapshots__/BackupScreen.test.tsx.snap | 12 +- .../src/screens/SettingsCloudBackupStatus.tsx | 6 +- .../mobile/src/screens/TokenDetailsScreen.tsx | 48 +- .../src/utils/useOpenBackupReminderModal.ts | 4 +- apps/web/.env | 10 +- apps/web/.env.production | 1 + apps/web/cypress/support/e2e.ts | 2 +- apps/web/package.json | 9 +- apps/web/public/nfts-sitemap.xml | 308 +- apps/web/public/pools-sitemap.xml | 3892 ++++++++++--- apps/web/public/tokens-sitemap.xml | 4932 ++++++++++++++--- .../AccountDrawer/AuthenticatedHeader.tsx | 2 +- .../components/AccountDrawer/DefaultMenu.tsx | 3 +- .../components/AccountDrawer/IconButton.tsx | 4 +- .../Activity/OffchainActivityModal.tsx | 6 +- .../OffchainActivityModal.test.tsx.snap | 18 +- .../MiniPortfolio/Activity/parseLocal.ts | 1 + .../MiniPortfolio/Activity/parseRemote.tsx | 2 +- .../MiniPortfolio/Activity/utils.ts | 12 +- .../Pools/useMultiChainPositions.tsx | 4 +- .../MiniPortfolio/Tokens/TokensTab.tsx | 6 +- .../__snapshots__/PortfolioLogo.test.tsx.snap | 4 +- .../src/components/AccountDrawer/index.tsx | 11 +- apps/web/src/components/AddressQRModal.tsx | 2 + .../components/Charts/VolumeChart/index.tsx | 1 + .../components/Charts/VolumeChart/utils.ts | 2 +- apps/web/src/components/Charts/utils.tsx | 1 + .../components/ConfirmSwapModal/Pending.tsx | 2 +- .../ConfirmSwapModal/ProgressIndicator.tsx | 4 +- .../src/components/ConfirmSwapModal/Step.tsx | 25 +- .../src/components/ConfirmSwapModal/index.tsx | 8 +- .../LimitPriceInputLabel.tsx | 2 +- .../LimitPriceInputPanel.tsx | 2 +- .../SwapCurrencyInputPanel.tsx | 2 +- .../components/CurrencyInputPanel/index.tsx | 2 +- .../src/components/DropdownSelector/index.tsx | 4 +- .../FeatureFlagModal/FeatureFlagModal.tsx | 4 + .../Liquidity/FeeTierSearchModal.tsx | 308 + .../LiquidityPositionAmountsTile.tsx | 63 + .../Liquidity/LiquidityPositionCard.tsx | 1 + .../Liquidity/LiquidityPositionInfo.tsx | 13 +- .../LiquidityPositionInfoBadges.test.tsx | 10 +- .../Liquidity/LiquidityPositionInfoBadges.tsx | 42 +- .../LiquidityPositionPriceRangeTile.tsx | 130 + .../src/components/Liquidity/PositionNFT.tsx | 79 + apps/web/src/components/Liquidity/utils.tsx | 87 +- apps/web/src/components/Logo/DoubleLogo.tsx | 2 +- .../NavBar/NavDropdown/NavDropdown.tsx | 11 +- .../NavBar/SearchBar/SuggestionRow.tsx | 45 +- .../SearchBarDropdown.test.tsx.snap | 6 +- .../NavBar/accountCTAsExperimentUtils.ts | 10 - .../NavBar/accountCTAsExperimentUtils.tsx | 25 + apps/web/src/components/NavBar/index.tsx | 5 +- apps/web/src/components/PercentInput.tsx | 16 +- .../Pools/PoolDetails/ChartSection/index.tsx | 2 + .../PoolDetails/PoolDetailsStatsButtons.tsx | 2 +- .../PoolDetailsHeader.test.tsx.snap | 2 +- .../PoolDetailsLink.test.tsx.snap | 2 +- .../PoolDetailsPositionTable.test.tsx.snap | 6 +- .../__snapshots__/PoolTable.test.tsx.snap | 2 +- .../src/components/Popups/PopupContent.tsx | 16 +- .../SearchModal/CurrencySearchModal.tsx | 8 +- .../Settings/MultipleRoutingOptions.tsx | 4 +- .../src/components/Settings/index.test.tsx | 6 +- apps/web/src/components/Settings/index.tsx | 4 +- apps/web/src/components/Table/index.tsx | 6 +- .../TokenSafety/TokenSafetyModal.tsx | 30 +- apps/web/src/components/TokenSafety/index.tsx | 19 +- .../TokenDetails/ChartSection/index.tsx | 59 +- .../components/Tokens/TokenDetails/index.tsx | 50 +- .../TokenDetailsPoolsTable.test.tsx.snap | 2 +- .../TokenTable/VolumeTimeFrameSelector.tsx | 1 + .../components/Tokens/TokenTable/index.tsx | 2 +- apps/web/src/components/Tooltip.tsx | 3 +- .../web/src/components/Web3Provider/index.tsx | 2 +- .../Web3Provider/{wagmi.ts => wagmiConfig.ts} | 0 apps/web/src/components/Web3Status/index.tsx | 2 +- .../addLiquidity/AddLiquidityContext.tsx | 14 +- .../src/components/addLiquidity/InputForm.tsx | 48 +- .../web/src/components/addLiquidity/hooks.tsx | 116 +- .../src/components/swap/SwapHeader.test.tsx | 4 +- apps/web/src/components/swap/SwapLineItem.tsx | 11 +- .../components/swap/SwapModalHeaderAmount.tsx | 4 +- .../src/components/swap/SwapPreview.test.tsx | 16 +- apps/web/src/components/swap/SwapPreview.tsx | 6 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 10 +- .../__snapshots__/SwapPreview.test.tsx.snap | 20 +- apps/web/src/components/swap/constants.ts | 5 - apps/web/src/constants/chains.ts | 17 +- apps/web/src/constants/routing.test.ts | 2 +- apps/web/src/graphql/data/ConversionRate.ts | 19 - .../graphql/data/apollo/AdaptiveRefetch.tsx | 2 +- .../apollo/AdaptiveTokenBalancesProvider.tsx | 10 + .../apollo/TokenBalancesProvider.test.tsx | 2 +- .../data/apollo/TokenBalancesProvider.tsx | 53 +- .../useReportTotalBalancesUsdForAnalytics.ts | 36 + .../apollo/useTotalBalancesUsdForAnalytics.ts | 9 + apps/web/src/graphql/data/types.ts | 4 +- apps/web/src/graphql/data/util.tsx | 5 +- apps/web/src/hooks/useConfirmModalState.ts | 7 +- apps/web/src/hooks/useEthersProvider.ts | 2 +- apps/web/src/hooks/useKeyPress.ts | 2 +- apps/web/src/hooks/useSelectChain.ts | 4 +- apps/web/src/hooks/useSwapTaxes.ts | 1 + apps/web/src/hooks/useSwitchChain.ts | 6 +- apps/web/src/hooks/useTokenBalances.test.ts | 6 +- apps/web/src/hooks/useTokenBalances.ts | 2 +- apps/web/src/hooks/useTransactionGasFee.ts | 4 +- apps/web/src/hooks/useUSDTokenUpdater.ts | 2 +- apps/web/src/hooks/useUniswapXSwapCallback.ts | 29 +- apps/web/src/hooks/useUniversalRouter.ts | 2 +- apps/web/src/hooks/useUnmountingAnimation.ts | 2 +- .../hooks/routing/useRoutingAPIArguments.ts | 21 +- apps/web/src/lib/hooks/useBlockNumber.tsx | 2 +- apps/web/src/lib/hooks/useInterval.ts | 2 +- .../web/src/lib/hooks/useTokenList/sorting.ts | 2 +- apps/web/src/lib/utils/analytics.ts | 86 +- apps/web/src/nft/components/bag/BagRow.tsx | 2 +- .../src/nft/components/bag/ButtonStates.tsx | 3 +- apps/web/src/nft/components/card/utils.tsx | 7 +- .../src/nft/components/collection/Sweep.tsx | 1 + .../nft/components/details/AssetDetails.tsx | 1 + apps/web/src/nft/hooks/useSendTransaction.ts | 2 +- apps/web/src/nft/pages/profile/index.tsx | 3 +- apps/web/src/nft/utils/urlParams.ts | 2 +- .../pages/AddLiquidity/AddLiquidityModal.tsx | 7 +- apps/web/src/pages/AddLiquidity/index.tsx | 15 +- apps/web/src/pages/AddLiquidityV2/index.tsx | 15 +- apps/web/src/pages/App/Header.tsx | 9 +- apps/web/src/pages/Landing/LandingV2.tsx | 10 +- apps/web/src/pages/Landing/sections/Hero.tsx | 29 +- apps/web/src/pages/LegacyPool/NewPosition.tsx | 21 - .../web/src/pages/LegacyPool/PositionPage.tsx | 85 +- apps/web/src/pages/MigrateV3/index.tsx | 95 +- .../src/pages/Pool/Positions/PositionPage.tsx | 196 + .../pages/Pool/Positions/V2PositionPage.tsx | 175 + .../pages/Pool/Positions/create/AddHook.tsx | 60 + .../Pool/Positions/create/CreatePosition.tsx | 242 + .../create/CreatePositionContext.tsx | 49 + .../create/CreatePositionContextProvider.tsx | 35 + .../pages/Pool/Positions/create/EditStep.tsx | 93 + .../Positions/create/RangeSelectionStep.tsx | 234 + .../Pool/Positions/create/SelectTokenStep.tsx | 301 + .../src/pages/Pool/Positions/create/hooks.tsx | 28 + .../pages/Pool/Positions/create/shared.tsx | 50 + .../src/pages/Pool/Positions/create/types.ts | 51 + apps/web/src/pages/Pool/Positions/index.tsx | 31 +- apps/web/src/pages/RemoveLiquidity/index.tsx | 15 +- apps/web/src/pages/RouteDefinitions.tsx | 32 +- apps/web/src/pages/Swap/Buy/BuyFormButton.tsx | 17 +- .../src/pages/Swap/Buy/CountryListModal.tsx | 4 +- .../PredefinedAmount.test.tsx.snap | 4 +- .../pages/Swap/Limit/LimitExpirySection.tsx | 3 +- apps/web/src/pages/Swap/Limit/LimitForm.tsx | 67 +- .../pages/Swap/Send/SendCurrencyInputForm.tsx | 2 +- apps/web/src/pages/Swap/Send/SendForm.tsx | 15 +- .../SendCurrencyInputForm.test.tsx.snap | 6 +- .../SendRecipientForm.test.tsx.snap | 2 +- apps/web/src/pages/Swap/SwapForm.tsx | 131 +- apps/web/src/pages/Swap/index.tsx | 101 +- apps/web/src/pages/TokenDetails/index.tsx | 4 +- .../pages/__snapshots__/routes.test.ts.snap | 34 +- apps/web/src/pages/paths.ts | 6 +- apps/web/src/setupTests.ts | 3 + apps/web/src/state/activity/polling/orders.ts | 11 +- .../state/activity/polling/transactions.ts | 2 +- .../web/src/state/application/reducer.test.ts | 15 +- apps/web/src/state/application/reducer.ts | 9 +- apps/web/src/state/burn/hooks.tsx | 15 +- apps/web/src/state/burn/v3/hooks.tsx | 15 +- apps/web/src/state/claim/hooks.ts | 2 +- .../src/state/fiatOnRampTransactions/types.ts | 1 + apps/web/src/state/index.ts | 10 +- .../state/limit/expiryToDeadlineSeconds.ts | 1 + apps/web/src/state/limit/hooks.ts | 24 +- apps/web/src/state/migrations/14.ts | 2 +- apps/web/src/state/migrations/15.ts | 2 +- apps/web/src/state/migrations/16.ts | 2 +- apps/web/src/state/migrations/17.ts | 2 +- apps/web/src/state/migrations/18.ts | 2 +- apps/web/src/state/migrations/19.ts | 2 +- apps/web/src/state/migrations/20.ts | 2 +- apps/web/src/state/mint/hooks.tsx | 15 +- apps/web/src/state/mint/v3/hooks.tsx | 15 +- apps/web/src/state/routing/slice.ts | 14 +- apps/web/src/state/routing/types.ts | 126 +- .../state/routing/useRoutingAPITrade.test.ts | 2 + apps/web/src/state/routing/utils.test.ts | 2 + apps/web/src/state/routing/utils.ts | 69 +- apps/web/src/state/sagas/root.ts | 20 + .../src/state/sagas/transactions/swapSaga.ts | 319 ++ .../src/state/sagas/transactions/uniswapx.ts | 86 + .../web/src/state/sagas/transactions/utils.ts | 169 + .../src/state/sagas/transactions/wrapSaga.ts | 80 + apps/web/src/state/signatures/hooks.ts | 12 +- apps/web/src/state/signatures/parseRemote.ts | 1 + apps/web/src/state/signatures/types.ts | 2 + apps/web/src/state/swap/SwapContext.test.tsx | 16 +- apps/web/src/state/swap/SwapContext.tsx | 6 +- apps/web/src/state/swap/hooks.test.ts | 4 +- apps/web/src/state/swap/hooks.tsx | 72 +- apps/web/src/state/swap/types.ts | 10 +- apps/web/src/test-utils/bundle-size-test.ts | 4 +- apps/web/src/theme/index.tsx | 2 - apps/web/src/tracing/errors.ts | 2 +- .../src/types/{position.d.ts => position.ts} | 5 + apps/web/src/utils/chains.tsx | 2 +- apps/web/src/utils/formatNumbers.test.ts | 38 +- apps/web/src/utils/formatNumbers.ts | 37 +- apps/web/src/utils/getFetchPolicyForKey.ts | 16 - apps/web/src/utils/prices.ts | 2 +- apps/web/src/utils/transfer.ts | 2 +- dangerfile.ts | 2 +- .../__snapshots__/preset.test.ts.snap | 24 + packages/eslint-config/base.js | 1 + packages/eslint-config/native.js | 1 + packages/ui/src/assets/icons/check.svg | 14 +- packages/ui/src/assets/icons/ellipsis.svg | 3 +- packages/ui/src/assets/icons/triple-dots.svg | 1 - packages/ui/src/assets/index.ts | 1 + .../ui/src/assets/logos/png/across-logo.png | Bin 0 -> 1214 bytes .../UniversalImage/UniversalImage.tsx | 3 + .../internal/PlainImage.native.tsx | 3 +- .../UniversalImage/internal/PlainImage.tsx | 39 +- .../internal/PlainImage.web.tsx | 38 + .../ui/src/components/UniversalImage/types.ts | 5 +- .../ui/src/components/UniversalImage/utils.ts | 1 + packages/ui/src/components/icons/Check.tsx | 12 +- packages/ui/src/components/icons/Ellipsis.tsx | 2 +- .../ui/src/components/icons/HeartWithFill.tsx | 14 + .../ui/src/components/icons/TripleDots.tsx | 33 - packages/ui/src/components/icons/Unitag.tsx | 22 +- packages/ui/src/components/icons/exported.ts | 1 - packages/ui/src/components/icons/index.tsx | 1 + .../ui/src/components/menu/ContextMenu.tsx | 2 +- .../src/components/modal/AdaptiveWebModal.tsx | 1 + .../swipeablecards/SwipeableCard.web.tsx | 60 +- .../swipeablecards/SwipeableCardStack.web.tsx | 76 + packages/ui/src/components/text/Text.tsx | 29 +- packages/ui/src/index.ts | 1 + packages/ui/src/scripts/componentize-icons.ts | 1 + packages/ui/src/theme/color/colors.ts | 6 +- .../ui/src/utils/colors/getExtractedColors.ts | 4 +- packages/uniswap/jest-setup.js | 2 +- packages/uniswap/package.json | 10 +- .../ConfirmSwapModal/ProgressIndicator.tsx | 172 +- .../src/components/ConfirmSwapModal/Step.tsx | 173 - .../ConfirmSwapModal/steps/Approve.tsx | 53 + .../ConfirmSwapModal/steps/Permit.tsx | 33 + .../steps/StepRowSkeleton.tsx | 120 + .../ConfirmSwapModal/steps/Swap.tsx | 88 + .../ConfirmSwapModal/steps/Wrap.tsx | 32 + .../src/components/ConfirmSwapModal/types.ts | 6 + .../CurrencyInputPanel/CurrencyInputPanel.tsx | 13 +- .../CurrencyLogo/CurrencyLogo.test.tsx | 4 + .../CurrencyLogo/SplitLogo.test.tsx | 4 + .../CurrencyLogo/TokenLogo.test.tsx | 4 + .../src/components/CurrencyLogo/TokenLogo.tsx | 26 +- .../__snapshots__/CurrencyLogo.test.tsx.snap | 44 +- .../__snapshots__/SplitLogo.test.tsx.snap | 44 +- .../__snapshots__/TokenLogo.test.tsx.snap | 22 +- .../HorizontalTokenList.native.tsx | 58 + .../HorizontalTokenList.tsx | 13 + .../HorizontalTokenList.web.tsx} | 19 +- .../TokenSelector/TokenOptionItem.tsx | 4 +- .../TokenSectionBaseList.web.tsx | 11 +- .../TokenSelector/TokenSectionHeader.tsx | 11 +- .../TokenSelector/TokenSelector.tsx | 22 +- .../TokenSelector/TokenSelectorList.tsx | 31 +- .../TokenSelectorSearchResultsList.tsx | 4 + .../TokenSelectorSwapInputList.tsx | 2 +- .../TokenSelectorSwapOutputList.tsx | 45 +- .../src/components/TokenSelector/hooks.tsx | 175 +- .../suggestedTokensKeyExtractor.tsx | 5 - .../src/components/TokenSelector/types.ts | 4 + .../src/components/TokenSelector/utils.tsx | 114 +- .../dropdowns/ActionSheetDropdown.tsx | 4 +- .../src/components/modals/Modal.native.tsx | 2 + .../src/components/modals/Modal.web.tsx | 14 +- .../src/components/modals/ModalProps.tsx | 1 + .../src/components/modals/PaginatedModals.tsx | 4 +- .../modals/WarningModal/WarningModal.tsx | 168 +- packages/uniswap/src/constants/chains.ts | 5 +- packages/uniswap/src/constants/tokens.ts | 22 +- packages/uniswap/src/constants/urls.ts | 1 + .../useQueryWithImmediateGarbageCollection.ts | 2 +- .../apiClients/tradingApi/TradingApiClient.ts | 20 +- .../tradingApiSwappableTokenToCurrencyInfo.ts | 41 + .../apiClients/uniswapApi/useGasFeeQuery.ts | 2 +- .../graphql/uniswap-data-api/queries.graphql | 48 +- .../uniswap-data-api/web/ConvertWeb.graphql | 7 - packages/uniswap/src/data/rest/getPools.ts | 14 + .../uniswap/src/data/rest/getPositions.ts | 117 +- packages/uniswap/src/data/tradingApi/api.json | 2 +- .../data/tradingApi/modifyTradingApiTypes.mts | 92 + packages/uniswap/src/data/tradingApi/types.ts | 35 + packages/uniswap/src/data/types.ts | 5 +- packages/uniswap/src/entities/assets.ts | 4 +- .../src/extension/useIsChromeWindowFocused.ts | 4 +- .../src/features/address/ExplorerView.tsx | 2 +- .../useSwappableTokenWithHighestBalance.ts | 86 + .../uniswap/src/features/dataApi/balances.ts | 8 +- .../src/features/dataApi/searchTokens.ts | 2 +- .../src/features/dataApi/tokenProjects.ts | 2 +- .../uniswap/src/features/dataApi/topTokens.ts | 2 +- .../src/features/dataApi/utils.test.ts | 4 +- .../uniswap/src/features/dataApi/utils.ts | 8 +- .../src/features/fiatCurrency/conversion.ts | 4 +- .../uniswap/src/features/fiatOnRamp/hooks.ts | 2 +- packages/uniswap/src/features/gas/hooks.ts | 7 +- packages/uniswap/src/features/gas/types.ts | 2 +- .../uniswap/src/features/gating/configs.ts | 2 +- .../src/features/gating/experiments.ts | 5 + packages/uniswap/src/features/gating/flags.ts | 5 + .../features/language/LocalizationContext.tsx | 5 +- .../providers/FlashbotsRpcProvider.test.ts | 1 + .../src/features/search/searchHistorySlice.ts | 1 + .../src/features/telemetry/constants/trace.ts | 4 + .../uniswap/src/features/telemetry/types.ts | 21 +- .../src/features/tokens/TokenWarningModal.tsx | 319 +- .../features/tokens/deprecatedSafetyUtils.ts | 2 + packages/uniswap/src/features/tokens/hooks.ts | 4 +- .../src/features/tokens/safetyUtils.test.ts | 10 +- .../src/features/tokens/safetyUtils.ts | 13 +- .../src/features/tokens/slice/hooks.ts | 2 +- .../src/features/tokens/useCurrencyInfo.ts | 2 +- .../DecimalPadInput/DecimalPad.native.tsx | 36 +- .../DecimalPadInput/DecimalPadInput.tsx | 86 +- .../transactions/DecimalPadInput/types.ts | 3 +- .../TransactionDetails/TransactionDetails.tsx | 8 + .../TransactionModal/TransactionModal.web.tsx | 2 + .../TransactionModalContext.tsx | 21 + .../TransactionModalProps.tsx | 1 + .../transactions/hooks/useUSDTokenUpdater.ts | 2 +- .../modals/BlockedAddressModal.tsx | 2 +- .../transactions/modals/ViewOnlyModal.tsx | 4 +- .../src/features/transactions/selectors.ts | 5 +- .../src/features/transactions/send/types.ts | 4 +- .../src/features/transactions/slice.ts | 9 + .../features/transactions/swap/SwapFlow.tsx | 6 +- .../features/transactions/swap/analytics.ts | 12 +- .../swap/contexts/SwapFormContext.tsx | 41 +- .../transactions/swap/form/SwapFormButton.tsx | 64 +- .../transactions/swap/form/SwapFormScreen.tsx | 29 +- .../swap/form/SwapFormSettings.tsx | 2 +- .../swap/form/SwapTokenSelector.tsx | 36 +- .../form/footer/GasAndWarningRows.web.tsx | 2 +- .../swap/hooks/useAcceptedTrade.ts | 2 +- .../swap/hooks/useDerivedSwapInfo.ts | 1 + .../swap/hooks/usePermit2Signature.ts | 2 +- .../swap/hooks/useSetTradeSlippage.ts | 5 + .../swap/hooks/useSwapPrefilledState.ts | 1 + .../swap/hooks/useSwapTxAndGasInfo.test.ts | 26 +- .../swap/hooks/useSwapTxAndGasInfo.ts | 35 +- .../swap/hooks/useSwapWarnings.test.ts | 3 +- .../swap/hooks/useTokenApprovalInfo.test.ts | 8 +- .../swap/hooks/useTokenApprovalInfo.ts | 27 +- .../transactions/swap/hooks/useTrade.ts | 9 +- .../hooks/useTransactionRequestInfo.test.ts | 123 + .../swap/hooks/useTransactionRequestInfo.ts | 69 +- .../transactions/swap/hooks/useUSDCPrice.ts | 4 +- .../swap/hooks/useWrapTransactionRequest.ts | 2 +- .../swap/modals/AcrossRoutingInfo.tsx | 77 + .../swap/modals/FeeOnTransferWarning.tsx | 2 +- .../swap/modals/NetworkFeeWarning.tsx | 2 +- .../swap/modals/PriceImpactWarning.tsx | 2 +- .../swap/modals/SwapFeeWarning.tsx | 2 +- .../swap/modals/SwapWarningModal.tsx | 4 +- .../transactions/swap/modals/UniswapXInfo.tsx | 2 +- .../swap/review/EstimatedTime.tsx | 48 + .../swap/review/MaxSlippageRow.tsx | 12 +- .../transactions/swap/review/SwapDetails.tsx | 39 +- .../swap/review/SwapReviewScreen.tsx | 126 +- .../swap/review/TransactionAmountsReview.tsx | 31 +- .../settings/configs/ProtocolPreference.tsx | 1 + .../swap/settings/configs/Slippage.native.tsx | 12 +- .../transactions/swap/types/swapCallback.ts | 9 +- .../swap/types/swapTxAndGasInfo.ts | 107 +- .../features/transactions/swap/types/trade.ts | 60 +- .../transactions/swap/types/wrapCallback.ts | 7 +- .../features/transactions/swap/utils/asset.ts | 15 + ...st.ts => generateTransactionSteps.test.ts} | 127 +- ...apSteps.ts => generateTransactionSteps.ts} | 120 +- .../transactions/swap/utils/getSwapFeeUsd.ts | 4 +- .../transactions/swap/utils/routing.ts | 4 + .../features/transactions/swap/utils/trade.ts | 36 +- .../transactions/swap/utils/tradingApi.ts | 37 +- .../transactions/types/transactionDetails.ts | 30 +- .../uniswap/src/i18n/i18n-setup-interface.tsx | 25 +- .../src/i18n/locales/source/en-US.json | 44 +- .../src/i18n/locales/translations/af-ZA.json | 7 + .../src/i18n/locales/translations/ar-SA.json | 7 + .../src/i18n/locales/translations/ca-ES.json | 7 + .../src/i18n/locales/translations/cs-CZ.json | 7 + .../src/i18n/locales/translations/da-DK.json | 7 + .../src/i18n/locales/translations/de-DE.json | 7 + .../src/i18n/locales/translations/el-GR.json | 7 + .../src/i18n/locales/translations/es-419.json | 2222 ++++++++ .../src/i18n/locales/translations/es-ES.json | 7 + .../src/i18n/locales/translations/fi-FI.json | 7 + .../src/i18n/locales/translations/fil-PH.json | 2222 ++++++++ .../src/i18n/locales/translations/fr-FR.json | 7 + .../src/i18n/locales/translations/he-IL.json | 7 + .../src/i18n/locales/translations/hi-IN.json | 7 + .../src/i18n/locales/translations/hu-HU.json | 7 + .../src/i18n/locales/translations/id-ID.json | 7 + .../src/i18n/locales/translations/it-IT.json | 7 + .../src/i18n/locales/translations/ja-JP.json | 7 + .../src/i18n/locales/translations/ko-KR.json | 7 + .../src/i18n/locales/translations/ms-MY.json | 7 + .../src/i18n/locales/translations/nl-NL.json | 7 + .../src/i18n/locales/translations/no-NO.json | 7 + .../src/i18n/locales/translations/pl-PL.json | 7 + .../src/i18n/locales/translations/pt-BR.json | 7 + .../src/i18n/locales/translations/pt-PT.json | 7 + .../src/i18n/locales/translations/ro-RO.json | 7 + .../src/i18n/locales/translations/ru-RU.json | 7 + .../src/i18n/locales/translations/sl-SI.json | 7 + .../src/i18n/locales/translations/sr-SP.json | 7 + .../src/i18n/locales/translations/sv-SE.json | 7 + .../src/i18n/locales/translations/sw-TZ.json | 7 + .../src/i18n/locales/translations/th-TH.json | 7 + .../src/i18n/locales/translations/tr-TR.json | 7 + .../src/i18n/locales/translations/uk-UA.json | 7 + .../src/i18n/locales/translations/ur-PK.json | 7 + .../src/i18n/locales/translations/vi-VN.json | 7 + .../src/i18n/locales/translations/zh-CN.json | 7 + .../src/i18n/locales/translations/zh-TW.json | 7 + packages/uniswap/src/test/fixtures/permit.ts | 16 + .../uniswap/src/test/fixtures/tradingApi.ts | 2 +- .../src/test/fixtures/transactions/swap.ts | 17 +- .../src/test/fixtures/wallet/balances.ts | 3 +- packages/uniswap/src/test/mocks/locale.ts | 165 +- packages/uniswap/src/theme/heights.ts | 1 + .../uniswap/src/types/screens/extension.ts | 1 + packages/uniswap/src/types/screens/mobile.ts | 4 +- .../uniswap/src/utils/clipboard.native.ts | 1 + packages/uniswap/src/utils/currency.test.ts | 11 +- packages/uniswap/tsconfig.json | 3 +- packages/utilities/package.json | 2 +- packages/utilities/src/environment/env.web.ts | 6 + packages/wallet/jest-setup.js | 2 +- packages/wallet/package.json | 4 +- .../CurrencyLogo/LogoWithTxStatus.test.tsx | 4 + .../CurrencyLogo/LogoWithTxStatus.tsx | 2 + .../LogoWithTxStatus.test.tsx.snap | 22 +- .../RecipientSelectSpeedBumps.tsx | 16 +- .../modals/NewAddressWarningModal.tsx | 6 +- .../WalletPreviewCard/WalletPreviewCard.tsx | 2 +- .../WalletPreviewCard.test.tsx.snap | 100 +- .../components}/introCards/IntroCard.test.tsx | 4 +- .../src/components}/introCards/IntroCard.tsx | 36 +- .../components}/introCards/IntroCardStack.tsx | 10 +- .../introCards/useSharedIntroCards.ts | 64 + .../src/components/modals/InfoLinkModal.tsx | 3 + .../src/components/nfts/NFTHiddenRow.tsx | 10 +- .../src/components/nfts/ShowNFTModal.tsx | 2 +- .../components/text/RelativeChange.test.tsx | 3 - .../src/contexts/WalletNavigationContext.tsx | 2 +- .../wallet/src/features/activity/hooks.ts | 8 +- packages/wallet/src/features/auth/saga.ts | 2 + .../src/features/behaviorHistory/slice.ts | 6 + .../wallet/src/features/gas/adjustGasFee.ts | 2 +- packages/wallet/src/features/gas/hooks.ts | 8 +- .../src/features/images/ElementAfterText.tsx | 39 + .../wallet/src/features/images/RemoteSvg.tsx | 3 +- packages/wallet/src/features/nfts/utils.ts | 2 +- .../components/NotificationToast.tsx | 35 +- .../components/SwapPendingNotification.tsx | 1 + .../src/features/notifications/selectors.ts | 2 +- .../src/features/notifications/utils.test.ts | 11 +- .../src/features/notifications/utils.ts | 1 + .../src/features/portfolio/AnimatedNumber.tsx | 2 +- .../features/portfolio/HiddenTokensRow.tsx | 40 +- .../features/portfolio/PortfolioBalance.tsx | 8 +- .../SummaryCards/DetailsModal/HeaderLogo.tsx | 3 + .../DetailsModal/TransactionDetailsModal.tsx | 6 +- .../SwapTransactionDetails.test.tsx.snap | 4 +- .../TransactionDetailsModal.test.tsx.snap | 29 +- .../SummaryCards/DetailsModal/types.ts | 5 + .../SummaryItems/TransactionSummaryLayout.tsx | 18 +- .../transactions/SummaryCards/utils.ts | 1 + .../TransactionHistoryUpdater.tsx | 12 +- .../transactions/cancelTransactionSaga.ts | 8 +- .../transactions/getAmountsFromTrade.ts | 10 +- .../extractFiatOnRampTransactionDetails.ts | 2 +- .../conversion/extractUniswapXOrderDetails.ts | 2 + .../conversion/parseTradeTransaction.ts | 10 +- .../features/transactions/history/utils.ts | 3 + .../wallet/src/features/transactions/hooks.ts | 17 +- .../useAllTransactionsBetweenAddresses.ts | 2 +- .../transactions/replaceTransactionSaga.ts | 3 +- .../transactions/send/SendReviewDetails.tsx | 15 +- .../send/hooks/useSendTransactionRequest.ts | 18 +- .../send/hooks/useSendWarnings.ts | 2 +- .../transactions/sendTransactionSaga.test.ts | 1 + .../transactions/sendTransactionSaga.ts | 24 +- .../transactions/swap/BridgingModal.tsx | 74 + .../swap/createSwapFormFromTxDetails.ts | 18 +- .../swap/hooks/useMostRecentSwapTx.ts | 9 +- .../swap/hooks/useSwapCallback.ts | 11 +- .../swap/modals/QueuedOrderModal.tsx | 2 +- .../swap/modals/SwapProtectionModal.tsx | 2 +- .../transactions/swap/submitOrderSaga.test.ts | 76 +- .../transactions/swap/submitOrderSaga.ts | 40 +- .../transactions/swap/swapSaga.test.ts | 37 +- .../features/transactions/swap/swapSaga.ts | 53 +- .../transactions/swap/wrapSaga.test.ts | 1 + .../features/transactions/swap/wrapSaga.ts | 7 +- .../transactionWatcherSaga.test.ts | 2 + .../transactions/transactionWatcherSaga.ts | 114 +- .../features/unitags/ClaimUnitagContent.tsx | 64 +- .../src/features/unitags/UnitagInfoModal.tsx | 2 +- .../features/unitags/useUnitagClaimHandler.ts | 30 +- packages/wallet/src/features/wallet/hooks.ts | 2 +- packages/wallet/src/utils/linking.ts | 6 +- packages/wallet/src/utils/useNoYoloParser.ts | 2 +- yarn.lock | 105 +- 629 files changed, 21575 insertions(+), 4929 deletions(-) create mode 100644 CODEOWNERS create mode 100644 apps/extension/src/app/features/home/introCards/HomeIntroCardStack.tsx create mode 100644 apps/extension/src/app/features/onboarding/ClaimUnitagScreen.tsx delete mode 100644 apps/mobile/src/app/modals/HiddenTokenInfoModal.tsx create mode 100644 apps/web/src/components/Liquidity/FeeTierSearchModal.tsx create mode 100644 apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx create mode 100644 apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx create mode 100644 apps/web/src/components/Liquidity/PositionNFT.tsx delete mode 100644 apps/web/src/components/NavBar/accountCTAsExperimentUtils.ts create mode 100644 apps/web/src/components/NavBar/accountCTAsExperimentUtils.tsx rename apps/web/src/components/Web3Provider/{wagmi.ts => wagmiConfig.ts} (100%) delete mode 100644 apps/web/src/graphql/data/ConversionRate.ts create mode 100644 apps/web/src/graphql/data/apollo/AdaptiveTokenBalancesProvider.tsx create mode 100644 apps/web/src/graphql/data/apollo/useReportTotalBalancesUsdForAnalytics.ts create mode 100644 apps/web/src/graphql/data/apollo/useTotalBalancesUsdForAnalytics.ts delete mode 100644 apps/web/src/pages/LegacyPool/NewPosition.tsx create mode 100644 apps/web/src/pages/Pool/Positions/PositionPage.tsx create mode 100644 apps/web/src/pages/Pool/Positions/V2PositionPage.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/AddHook.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/CreatePositionContextProvider.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/EditStep.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/SelectTokenStep.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/hooks.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/shared.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/types.ts create mode 100644 apps/web/src/state/sagas/root.ts create mode 100644 apps/web/src/state/sagas/transactions/swapSaga.ts create mode 100644 apps/web/src/state/sagas/transactions/uniswapx.ts create mode 100644 apps/web/src/state/sagas/transactions/utils.ts create mode 100644 apps/web/src/state/sagas/transactions/wrapSaga.ts rename apps/web/src/types/{position.d.ts => position.ts} (84%) delete mode 100644 apps/web/src/utils/getFetchPolicyForKey.ts delete mode 100644 packages/ui/src/assets/icons/triple-dots.svg create mode 100644 packages/ui/src/assets/logos/png/across-logo.png create mode 100644 packages/ui/src/components/UniversalImage/internal/PlainImage.web.tsx create mode 100644 packages/ui/src/components/icons/HeartWithFill.tsx delete mode 100644 packages/ui/src/components/icons/TripleDots.tsx create mode 100644 packages/ui/src/components/swipeablecards/SwipeableCardStack.web.tsx delete mode 100644 packages/uniswap/src/components/ConfirmSwapModal/Step.tsx create mode 100644 packages/uniswap/src/components/ConfirmSwapModal/steps/Approve.tsx create mode 100644 packages/uniswap/src/components/ConfirmSwapModal/steps/Permit.tsx create mode 100644 packages/uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton.tsx create mode 100644 packages/uniswap/src/components/ConfirmSwapModal/steps/Swap.tsx create mode 100644 packages/uniswap/src/components/ConfirmSwapModal/steps/Wrap.tsx create mode 100644 packages/uniswap/src/components/ConfirmSwapModal/types.ts create mode 100644 packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.native.tsx create mode 100644 packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.tsx rename packages/uniswap/src/components/TokenSelector/{renderSuggestedTokenItem.tsx => HorizontalTokenList/HorizontalTokenList.web.tsx} (52%) delete mode 100644 packages/uniswap/src/components/TokenSelector/suggestedTokensKeyExtractor.tsx create mode 100644 packages/uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo.ts delete mode 100644 packages/uniswap/src/data/graphql/uniswap-data-api/web/ConvertWeb.graphql create mode 100644 packages/uniswap/src/data/rest/getPools.ts create mode 100644 packages/uniswap/src/data/tradingApi/modifyTradingApiTypes.mts create mode 100644 packages/uniswap/src/data/tradingApi/types.ts create mode 100644 packages/uniswap/src/features/bridging/hooks/useSwappableTokenWithHighestBalance.ts rename packages/{wallet => uniswap}/src/features/transactions/swap/hooks/useSwapPrefilledState.ts (98%) create mode 100644 packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts create mode 100644 packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx create mode 100644 packages/uniswap/src/features/transactions/swap/review/EstimatedTime.tsx create mode 100644 packages/uniswap/src/features/transactions/swap/utils/asset.ts rename packages/uniswap/src/features/transactions/swap/utils/{generateSwapSteps.test.ts => generateTransactionSteps.test.ts} (71%) rename packages/uniswap/src/features/transactions/swap/utils/{generateSwapSteps.ts => generateTransactionSteps.ts} (63%) create mode 100644 packages/uniswap/src/i18n/locales/translations/es-419.json create mode 100644 packages/uniswap/src/i18n/locales/translations/fil-PH.json create mode 100644 packages/uniswap/src/test/fixtures/permit.ts create mode 100644 packages/uniswap/src/theme/heights.ts rename {apps/mobile/src/components/home => packages/wallet/src/components}/introCards/IntroCard.test.tsx (74%) rename {apps/mobile/src/components/home => packages/wallet/src/components}/introCards/IntroCard.tsx (70%) rename {apps/mobile/src/components/home => packages/wallet/src/components}/introCards/IntroCardStack.tsx (59%) create mode 100644 packages/wallet/src/components/introCards/useSharedIntroCards.ts create mode 100644 packages/wallet/src/features/images/ElementAfterText.tsx create mode 100644 packages/wallet/src/features/transactions/swap/BridgingModal.tsx rename {apps/mobile => packages/wallet}/src/features/unitags/useUnitagClaimHandler.ts (59%) diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..f70773659eb --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @uniswap/web-admins diff --git a/RELEASE b/RELEASE index 4d6ff306cf4..2d0220cbefa 100644 --- a/RELEASE +++ b/RELEASE @@ -1,11 +1,63 @@ -We are back with some new updates! Here’s the latest: +IPFS hash of the deployment: +- CIDv0: `Qmd28dj63MAN4SSYPnXMSDe5bKUbnwSFKijHz59isELY4t` +- CIDv1: `bafybeig2edl4hxzvem7w6ujx735fox2jnpdaizovofieutl3rvawydroqu` -Manage dapp connections - Users can now see all dapps they’re connected to, and disconnect to one or all of them. +The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). -Report Spam NFTs - You can now report spam NFTs and hide them from your feed and activity. +You can also access the Uniswap Interface from an IPFS gateway. +**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. +**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org). +Your Uniswap settings are never remembered across different URLs. + +IPFS gateways: +- https://bafybeig2edl4hxzvem7w6ujx735fox2jnpdaizovofieutl3rvawydroqu.ipfs.dweb.link/ +- https://bafybeig2edl4hxzvem7w6ujx735fox2jnpdaizovofieutl3rvawydroqu.ipfs.cf-ipfs.com/ +- [ipfs://Qmd28dj63MAN4SSYPnXMSDe5bKUbnwSFKijHz59isELY4t/](ipfs://Qmd28dj63MAN4SSYPnXMSDe5bKUbnwSFKijHz59isELY4t/) + +## 5.49.0 (2024-10-01) + + +### Features + +* **web:** [1/2] fee tier selection modal (#12092) fa1f56d +* **web:** [2/2] fee tier selection modal (create new) (#12110) 780bbc0 +* **web:** add fee tier and price range selection to migrate page (#12064) f417a3f +* **web:** add fiat values props to swap logs in new saga (#12215) 857119c +* **web:** add price impact events (#12208) 30b8c39 +* **web:** add total_balances_usd prop to swap logs in new saga (#12173) 79a0b17 +* **web:** add UniswapX priority orders in swap flow (#12088) d55a9bd +* **web:** add uniswapx saga to shared web flow (#11973) d10498e +* **web:** add url param to create positions page (#12212) b89e5d1 +* **web:** add wrap saga for shared flow (#11971) f14a41a +* **web:** adding multiple protocols to create page (#12132) b4bff49 +* **web:** changing create page based on version (#12136) bc16817 +* **web:** classic swap saga (#11922) f74c332 +* **web:** keep input to all networks unless output selected (#12274) e43b95d +* **web:** log SWAP_MODIFIED_IN_WALLET event in new swap saga (#12247) 16e9f56 +* **web:** log SWAP_SIGNED event in new swag saga (#12170) 2494b9d +* **web:** log UniswapXOrderPostError and UniswapXSignatureRequested in new saga (#12114) 454666b +* **web:** log UniswapXOrderSubmitted in new saga (#12113) 9ae3373 +* **web:** pulling from backend for create page (#12143) 8f6aa1e +* **web:** swap saga notifications (#12070) e28ec03 +* **web:** v2 position page refreshed (#11972) da6c705 +* **web:** v4 Position detail page (#11904) a735564 + + +### Bug Fixes + +* **web:** fix AddressQRModal eyeSize (#12318) 33f8160 +* **web:** fix language file mapping (#12207) bbfe66d +* **web:** fix overflow issue in search results (#12158) fbd7358 +* **web:** limit form button color should be neutralContrast (#12416) d923cdd +* **web:** shared swap landing page (#12026) fe2d0b6 +* **web:** token caching fix in mini portfolio (#12149) 4c2d402 +* **web:** token selector height (#12028) 3adb0d0 +* **web:** use percentFromFloat (#12142) 18f819b +* **web:** use swap saga prefilled state (#12082) c6bd810 + + +### Continuous Integration + +* **web:** update sitemaps d8b4a5a -Other changes: -- Added explainers for hidden tokens, popular tokens, and hidden NFTs -- Removed activity feed items related to any hidden NFTs -- Various bug fixes and performance improvements \ No newline at end of file diff --git a/VERSION b/VERSION index 81477f8933a..8fabc1e5adf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -extension/1.6.0 \ No newline at end of file +web/5.49.0 \ No newline at end of file diff --git a/apps/extension/jest-setup.js b/apps/extension/jest-setup.js index cd65e145579..5b05899861b 100644 --- a/apps/extension/jest-setup.js +++ b/apps/extension/jest-setup.js @@ -5,6 +5,7 @@ import { AppearanceSettingType } from 'wallet/src/features/appearance/slice' import { TextEncoder, TextDecoder } from 'util' import { mockSharedPersistQueryClientProvider } from 'uniswap/src/test/mocks/mockSharedPersistQueryClientProvider' import { mockUIAssets } from 'ui/src/test/mocks/mockUIAssets' +import { mockLocalizationContext } from 'uniswap/src/test/mocks/locale' process.env.IS_UNISWAP_EXTENSION = true @@ -72,6 +73,8 @@ jest.mock('wallet/src/features/appearance/hooks', () => { } }) +jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext({})) + jest.mock('uniswap/src/data/apiClients/SharedPersistQueryClientProvider', () => mockSharedPersistQueryClientProvider) mockUIAssets() diff --git a/apps/extension/package.json b/apps/extension/package.json index 516d29cda66..1933bc190fe 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -13,8 +13,8 @@ "@svgr/webpack": "8.0.1", "@tamagui/core": "1.108.4", "@types/uuid": "9.0.1", - "@uniswap/analytics-events": "2.36.0", - "@uniswap/uniswapx-sdk": "^2.1.0-beta.8", + "@uniswap/analytics-events": "2.37.0", + "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", "@uniswap/universal-router-sdk": "2.2.0", "@uniswap/v3-sdk": "3.14.0", "dotenv-webpack": "8.0.1", diff --git a/apps/extension/src/app/OnboardingApp.tsx b/apps/extension/src/app/OnboardingApp.tsx index 3d46a997ed0..cf5fd3b117c 100644 --- a/apps/extension/src/app/OnboardingApp.tsx +++ b/apps/extension/src/app/OnboardingApp.tsx @@ -9,6 +9,7 @@ import { PersistGate } from 'redux-persist/integration/react' import { ExtensionStatsigProvider } from 'src/app/StatsigProvider' import { GraphqlProvider } from 'src/app/apollo' import { ErrorElement } from 'src/app/components/ErrorElement' +import { ClaimUnitagScreen } from 'src/app/features/onboarding/ClaimUnitagScreen' import { Complete } from 'src/app/features/onboarding/Complete' import { CreateOnboardingSteps, @@ -55,6 +56,14 @@ const unsupportedRoute: RouteObject = { element: , } +const createSteps = { + [CreateOnboardingSteps.Password]: , + [CreateOnboardingSteps.ViewMnemonic]: , + [CreateOnboardingSteps.TestMnemonic]: , + [CreateOnboardingSteps.Naming]: , + [CreateOnboardingSteps.Complete]: , +} + const allRoutes = [ { path: '', @@ -66,15 +75,16 @@ const allRoutes = [ }, { path: OnboardingRoutes.Create, + element: , + }, + { + path: OnboardingRoutes.Claim, element: ( , - [CreateOnboardingSteps.ViewMnemonic]: , - [CreateOnboardingSteps.TestMnemonic]: , - [CreateOnboardingSteps.Naming]: , - [CreateOnboardingSteps.Complete]: , + [CreateOnboardingSteps.ClaimUnitag]: , + ...createSteps, }} /> ), diff --git a/apps/extension/src/app/features/accounts/AccountItem.tsx b/apps/extension/src/app/features/accounts/AccountItem.tsx index ee2bc8f56c8..8206f619652 100644 --- a/apps/extension/src/app/features/accounts/AccountItem.tsx +++ b/apps/extension/src/app/features/accounts/AccountItem.tsx @@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux' import { EditLabelModal } from 'src/app/features/accounts/EditLabelModal' import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions' import { ContextMenu, Flex, MenuContentItem, Text, TouchableArea } from 'ui/src' -import { CopySheets, Edit, TrashFilled, TripleDots } from 'ui/src/components/icons' +import { CopySheets, Edit, Ellipsis, TrashFilled } from 'ui/src/components/icons' import { iconSizes } from 'ui/src/theme' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' @@ -131,15 +131,15 @@ export function AccountItem({ address, onAccountSelect, balanceUSD }: AccountIte caption={t('account.recoveryPhrase.remove.mnemonic.description', { walletNames: [activeAccountDisplayName?.name ?? ''], })} - closeText={t('common.button.cancel')} - confirmText={t('common.button.continue')} + rejectText={t('common.button.cancel')} + acknowledgeText={t('common.button.continue')} icon={} isOpen={showRemoveWalletModal} modalName={ModalName.RemoveWallet} severity={WarningSeverity.High} title={t('account.wallet.remove.title', { name: displayName?.name ?? '' })} onClose={() => setShowRemoveWalletModal(false)} - onConfirm={onRemoveWallet} + onAcknowledge={onRemoveWallet} /> setShowEditLabelModal(false)} /> - + diff --git a/apps/extension/src/app/features/accounts/AccountSwitcherScreen.tsx b/apps/extension/src/app/features/accounts/AccountSwitcherScreen.tsx index eec406d1861..a28b459c628 100644 --- a/apps/extension/src/app/features/accounts/AccountSwitcherScreen.tsx +++ b/apps/extension/src/app/features/accounts/AccountSwitcherScreen.tsx @@ -166,15 +166,15 @@ export function AccountSwitcherScreen(): JSX.Element { /> } isOpen={showRemoveWalletModal} modalName={ModalName.RemoveWallet} severity={WarningSeverity.High} title={t('account.wallet.button.import')} onClose={() => setShowRemoveWalletModal(false)} - onConfirm={onNavigateToRemoveWallet} + onAcknowledge={onNavigateToRemoveWallet} /> existingAccount.address !== account.address) - : [] - - const activeConnected = updatedAccounts[0] - if (activeConnected) { - return { - ...initialState, - [dappUrl]: { - ...dappUrlState, - connectedAccounts: updatedAccounts, - activeConnectedAddress: activeConnected.address, - }, - } - } else { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { [dappUrl]: _, ...restState } = initialState - return restState + dappInfo.connectedAccounts = dappInfo.connectedAccounts.filter( + (existingAccount) => existingAccount.address !== account?.address, + ) + + const nextConnectedAccount = dappInfo.connectedAccounts[0] + + if (!nextConnectedAccount || !account) { + delete newState[dappUrl] + return newState + } + + if (dappInfo.activeConnectedAddress === account.address) { + dappInfo.activeConnectedAddress = nextConnectedAccount.address } + return newState } function removeAllDappConnections(): void { diff --git a/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx b/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx index d05cd53f4c9..21d4e86f69c 100644 --- a/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx +++ b/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx @@ -237,14 +237,14 @@ export function DappRequestFooter({ px="$spacing8" /> - diff --git a/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx b/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx index 03b87ec8e30..0b95b8b6833 100644 --- a/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx +++ b/apps/extension/src/app/features/onboarding/scan/ScanToOnboard.tsx @@ -156,7 +156,7 @@ export function ScanToOnboard(): JSX.Element { useEffect(() => { if (!isLoadingUUID) { qrScale.value = UNISWAP_LOGO_SCALE_DEFAULT - return + return undefined } const springConfig: SpringConfig = { diff --git a/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx b/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx index 6c3c754458e..a998a439b45 100644 --- a/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx +++ b/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx @@ -14,6 +14,7 @@ import { useUSDTokenUpdater } from 'uniswap/src/features/transactions/hooks/useU import { BlockedAddressWarning } from 'uniswap/src/features/transactions/modals/BlockedAddressWarning' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { useIsBlocked } from 'uniswap/src/features/trm/hooks' +import { WalletChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' @@ -168,7 +169,7 @@ export function SendFormScreen(): JSX.Element { py="$spacing12" {...inputShadowProps} > - + {!showRecipientSelector && ( <> @@ -184,7 +185,7 @@ export function SendFormScreen(): JSX.Element { /> )} - + )} diff --git a/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/internal/EllipsisDropdown.tsx b/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/internal/EllipsisDropdown.tsx index 8b32186707d..76491520f48 100644 --- a/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/internal/EllipsisDropdown.tsx +++ b/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/internal/EllipsisDropdown.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' import { removeAllDappConnectionsForAccount } from 'src/app/features/dapp/actions' import { ContextMenu, Flex, TouchableArea } from 'ui/src' -import { Power, TripleDots } from 'ui/src/components/icons' +import { Ellipsis, Power } from 'ui/src/components/icons' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { pushNotification } from 'wallet/src/features/notifications/slice' @@ -49,7 +49,7 @@ export function EllipsisDropdown(): JSX.Element { onLeftClick={true} > - + ) diff --git a/apps/extension/src/app/features/swap/SwapFlowScreen.tsx b/apps/extension/src/app/features/swap/SwapFlowScreen.tsx index e99658f21a3..fa626880567 100644 --- a/apps/extension/src/app/features/swap/SwapFlowScreen.tsx +++ b/apps/extension/src/app/features/swap/SwapFlowScreen.tsx @@ -1,9 +1,9 @@ import { useExtensionNavigation } from 'src/app/navigation/utils' import { Flex } from 'ui/src' import { useHighestBalanceNativeCurrencyId } from 'uniswap/src/features/dataApi/balances' +import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState' import { prepareSwapFormState } from 'uniswap/src/features/transactions/types/transactionState' import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow' -import { useSwapPrefilledState } from 'wallet/src/features/transactions/swap/hooks/useSwapPrefilledState' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' export function SwapFlowScreen(): JSX.Element { diff --git a/apps/extension/src/app/features/warnings/StorageWarningModal.tsx b/apps/extension/src/app/features/warnings/StorageWarningModal.tsx index 96bc0506afb..99f26f6fd56 100644 --- a/apps/extension/src/app/features/warnings/StorageWarningModal.tsx +++ b/apps/extension/src/app/features/warnings/StorageWarningModal.tsx @@ -19,15 +19,15 @@ export function StorageWarningModal({ isOnboarding }: StorageWarningModalProps): return ( { diff --git a/apps/extension/src/app/hooks/useOpeningKeyboardShortCut.ts b/apps/extension/src/app/hooks/useOpeningKeyboardShortCut.ts index 87433f2d9c0..eeb375d66f5 100644 --- a/apps/extension/src/app/hooks/useOpeningKeyboardShortCut.ts +++ b/apps/extension/src/app/hooks/useOpeningKeyboardShortCut.ts @@ -15,6 +15,7 @@ export enum State { type ReducerAction = { type: 'keyUp' | 'keyDown' | 'highlight'; key: string } | { type: 'highlight' } export const useOpeningKeyboardShortCut = (shortCutPressed: boolean): KeyboardKeyProps[] => { + // eslint-disable-next-line consistent-return const reducer = (state: KeyboardKeyProps[], action: ReducerAction): KeyboardKeyProps[] => { switch (action.type) { case 'keyDown': diff --git a/apps/extension/src/app/navigation/constants.ts b/apps/extension/src/app/navigation/constants.ts index 7ec2c232d30..afb596ab081 100644 --- a/apps/extension/src/app/navigation/constants.ts +++ b/apps/extension/src/app/navigation/constants.ts @@ -8,6 +8,7 @@ export enum TopLevelRoutes { export enum OnboardingRoutes { Import = 'import', Create = 'create', + Claim = 'claim', Scan = 'scan', Reset = 'reset', ResetScan = 'reset-scan', diff --git a/apps/extension/src/app/navigation/utils.ts b/apps/extension/src/app/navigation/utils.ts index 178842daea3..182e5ec25cb 100644 --- a/apps/extension/src/app/navigation/utils.ts +++ b/apps/extension/src/app/navigation/utils.ts @@ -150,7 +150,7 @@ export async function focusOrCreateTokensExploreTab({ currencyId }: { currencyId tags: { file: 'navigation/utils.ts', function: 'focusOrCreateTokensExploreTab' }, extra: { currencyId }, }) - return + return undefined } return focusOrCreateUniswapInterfaceTab({ diff --git a/apps/extension/src/contentScript/WindowEthereumProxy.ts b/apps/extension/src/contentScript/WindowEthereumProxy.ts index 2c479f5021d..b06f1360bb4 100644 --- a/apps/extension/src/contentScript/WindowEthereumProxy.ts +++ b/apps/extension/src/contentScript/WindowEthereumProxy.ts @@ -106,6 +106,7 @@ export class WindowEthereumProxy extends EventEmitter { ...ethereumRequest, requestId, }) + return Promise.resolve() } catch (error) { logger.info('WindowEthereumProxy.ts', 'request', 'Invalid request', args) diff --git a/apps/extension/src/manifest.json b/apps/extension/src/manifest.json index 0532e617ce2..b382fb9e62a 100644 --- a/apps/extension/src/manifest.json +++ b/apps/extension/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Uniswap Extension", "description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.", - "version": "1.6.0", + "version": "1.7.0", "minimum_chrome_version": "116", "icons": { "16": "assets/icon16.png", diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index 762e948f5c3..883b1405283 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -90,9 +90,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) { apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle" } -def devVersionName = "1.36" -def betaVersionName = "1.36" -def prodVersionName = "1.36" +def devVersionName = "1.37" +def betaVersionName = "1.37" +def prodVersionName = "1.37" android { ndkVersion rootProject.ext.ndkVersion diff --git a/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj b/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj index 4d50741caf4..2607152847c 100644 --- a/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj @@ -2167,7 +2167,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2220,7 +2220,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; @@ -2273,7 +2273,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; @@ -2326,7 +2326,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; @@ -2364,7 +2364,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2400,7 +2400,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; @@ -2435,7 +2435,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; @@ -2470,7 +2470,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; @@ -2517,7 +2517,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2563,7 +2563,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets; @@ -2609,7 +2609,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets; @@ -2655,7 +2655,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets; @@ -2697,7 +2697,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2740,7 +2740,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension; @@ -2783,7 +2783,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension; @@ -2826,7 +2826,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension; @@ -2862,7 +2862,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -2900,7 +2900,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -3078,7 +3078,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3122,7 +3122,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension; @@ -3222,7 +3222,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -3293,7 +3293,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension; @@ -3393,7 +3393,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -3464,7 +3464,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.36; + MARKETING_VERSION = 1.37; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension; diff --git a/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift b/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift index b74585300ec..d147fc40791 100644 --- a/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift +++ b/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift @@ -22,7 +22,7 @@ public class DataQueries { let tokens = graphQLResult.data?.tokens ?? [] let tokenResponses = tokens.map { let symbol = $0?.symbol - let name = $0?.project?.name + let name = $0?.name let chain = $0?.chain let address = $0?.address return TokenResponse(chain: chain?.rawValue ?? "", address: address, symbol: symbol ?? "", name: name ?? "") @@ -43,7 +43,7 @@ public class DataQueries { let topTokens = graphQLResult.data?.topTokens ?? [] let tokenResponses = topTokens.map { (tokenData) -> TokenResponse in let symbol = tokenData?.symbol - let name = tokenData?.project?.name + let name = tokenData?.name let chain = tokenData?.chain let address = tokenData?.address return TokenResponse(chain: chain?.rawValue ?? "", address: address, symbol: symbol ?? "", name: name ?? "") @@ -63,11 +63,11 @@ public class DataQueries { case .success(let graphQLResult): let token = graphQLResult.data?.token let symbol = token?.symbol - let name = token?.project?.name + let name = token?.name let logoUrl = token?.project?.logoUrl ?? nil - let markets = token?.project?.markets - let spotPrice = (markets != nil) && !markets!.isEmpty ? markets?[0]?.price?.value : nil - let pricePercentChange = (markets != nil) && !markets!.isEmpty ? markets?[0]?.pricePercentChange24h?.value : nil + let market = token?.market + let spotPrice = market?.price?.value + let pricePercentChange = market?.pricePercentChange?.value let tokenPriceResponse = TokenPriceResponse(chain: chain, address: address, symbol: symbol ?? "", name: name ?? "", logoUrl: logoUrl ?? "", spotPrice: spotPrice, pricePercentChange: pricePercentChange) continuation.resume(returning: tokenPriceResponse) case .failure(let error): @@ -109,7 +109,7 @@ public class DataQueries { $0?.tokenBalances?.forEach { tokenBalance in let value = tokenBalance?.denominatedValue?.value let token = tokenBalance?.token - let tokenResponse = TokenResponse(chain: token?.chain.rawValue ?? "", address: token?.address, symbol: token?.symbol ?? "", name: token?.project?.name ?? "") + let tokenResponse = TokenResponse(chain: token?.chain.rawValue ?? "", address: token?.address, symbol: token?.symbol ?? "", name: token?.name ?? "") let isSpam = token?.project?.isSpam ?? false if (!isSpam) { tokens[tokenResponse] = (tokens[tokenResponse] ?? 0) + (value ?? 0) diff --git a/apps/mobile/jest-setup.js b/apps/mobile/jest-setup.js index fa40bf39df6..208cbed9336 100644 --- a/apps/mobile/jest-setup.js +++ b/apps/mobile/jest-setup.js @@ -84,7 +84,7 @@ jest.mock('@react-navigation/elements', () => ({ require('react-native-reanimated').setUpTests() -jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext) +jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext({})) jest.mock('react-native/Libraries/Share/Share', () => ({ share: jest.fn(), diff --git a/apps/mobile/package.json b/apps/mobile/package.json index c079a2d81fb..ba96ad068c7 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -86,7 +86,7 @@ "@shopify/react-native-skia": "1.2.0", "@sparkfabrik/react-native-idfa-aaid": "1.2.0", "@uniswap/analytics": "1.7.0", - "@uniswap/analytics-events": "2.36.0", + "@uniswap/analytics-events": "2.37.0", "@uniswap/ethers-rs-mobile": "0.0.5", "@uniswap/sdk-core": "5.3.0", "@walletconnect/core": "2.11.2", diff --git a/apps/mobile/src/app/migrations.ts b/apps/mobile/src/app/migrations.ts index 96f656654ee..66ce24dc095 100644 --- a/apps/mobile/src/app/migrations.ts +++ b/apps/mobile/src/app/migrations.ts @@ -241,7 +241,7 @@ export const migrations = { 17: (state: any) => { const accounts: Record | undefined = state?.wallet?.accounts if (!accounts) { - return + return undefined } for (const account of Object.values(accounts)) { diff --git a/apps/mobile/src/app/modals/AppModals.tsx b/apps/mobile/src/app/modals/AppModals.tsx index e89bde7eb07..b7b36041b10 100644 --- a/apps/mobile/src/app/modals/AppModals.tsx +++ b/apps/mobile/src/app/modals/AppModals.tsx @@ -5,7 +5,6 @@ import { BackupReminderModal } from 'src/app/modals/BackupReminderModal' import { BackupWarningModal } from 'src/app/modals/BackupWarningModal' import { ExperimentsModal } from 'src/app/modals/ExperimentsModal' import { ExploreModal } from 'src/app/modals/ExploreModal' -import { HiddenTokenInfoModal } from 'src/app/modals/HiddenTokenInfoModal' import { KoreaCexTransferInfoModal } from 'src/app/modals/KoreaCexTransferInfoModal' import { LazyModalRenderer } from 'src/app/modals/LazyModalRenderer' import { SendTokenModal } from 'src/app/modals/SendTokenModal' @@ -36,10 +35,6 @@ export function AppModals(): JSX.Element { return ( <> - - - - diff --git a/apps/mobile/src/app/modals/BackupWarningModal.tsx b/apps/mobile/src/app/modals/BackupWarningModal.tsx index af26408413b..bffdea5c591 100644 --- a/apps/mobile/src/app/modals/BackupWarningModal.tsx +++ b/apps/mobile/src/app/modals/BackupWarningModal.tsx @@ -43,14 +43,14 @@ export function BackupWarningModal(): JSX.Element { ) } diff --git a/apps/mobile/src/app/modals/HiddenTokenInfoModal.tsx b/apps/mobile/src/app/modals/HiddenTokenInfoModal.tsx deleted file mode 100644 index 46b76f3e03a..00000000000 --- a/apps/mobile/src/app/modals/HiddenTokenInfoModal.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react' -import { useTranslation } from 'react-i18next' -import { useDispatch } from 'react-redux' -import { Action } from 'redux' -import { closeModal } from 'src/features/modals/modalSlice' -import { Button, Flex, Text, useSporeColors } from 'ui/src' -import { ShieldCheck } from 'ui/src/components/icons' -import { iconSizes } from 'ui/src/theme/iconSizes' -import { Modal } from 'uniswap/src/components/modals/Modal' -import { uniswapUrls } from 'uniswap/src/constants/urls' -import { ModalName } from 'uniswap/src/features/telemetry/constants' -import { openURL } from 'uniswap/src/utils/link' -import { logger } from 'utilities/src/logger/logger' - -export function HiddenTokenInfoModal(): JSX.Element { - const dispatch = useDispatch() - const color = useSporeColors() - const { t } = useTranslation() - - const onClose = (): void => { - dispatch(closeModal({ name: ModalName.HiddenTokenInfoModal })) - } - - const openUniswapURL = async (): Promise => { - try { - await openURL(uniswapUrls.helpArticleUrls.hiddenTokenInfo) - } catch (error) { - logger.error(error, { tags: { file: 'HiddenToeknInfoModal.tsx', function: 'openUniswapURL' } }) - } - } - - return ( - dispatch(closeModal({ name: ModalName.HiddenTokenInfoModal }))} - > - - - - - - - - {t('hidden.tokens.info.text.title')} - - {t('hidden.tokens.info.text.info')} - - - - - - - ) -} diff --git a/apps/mobile/src/app/modals/SwapModal.tsx b/apps/mobile/src/app/modals/SwapModal.tsx index 2a0340a0ec9..57f3793aa2e 100644 --- a/apps/mobile/src/app/modals/SwapModal.tsx +++ b/apps/mobile/src/app/modals/SwapModal.tsx @@ -7,8 +7,8 @@ import { selectModalState } from 'src/features/modals/selectModalState' import { useWalletRestore } from 'src/features/wallet/hooks' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { updateSwapStartTimestamp } from 'uniswap/src/features/timing/slice' +import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState' import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow' -import { useSwapPrefilledState } from 'wallet/src/features/transactions/swap/hooks/useSwapPrefilledState' export function SwapModal(): JSX.Element { const appDispatch = useDispatch() diff --git a/apps/mobile/src/app/navigation/NavBar.tsx b/apps/mobile/src/app/navigation/NavBar.tsx index 10945f923cc..982bfe45bac 100644 --- a/apps/mobile/src/app/navigation/NavBar.tsx +++ b/apps/mobile/src/app/navigation/NavBar.tsx @@ -1,8 +1,8 @@ import { SharedEventName } from '@uniswap/analytics-events' import { BlurView } from 'expo-blur' -import React, { memo, useCallback } from 'react' +import React, { memo, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { StyleSheet } from 'react-native' +import { LayoutChangeEvent, LayoutRectangle, StyleSheet } from 'react-native' import { TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler' import { cancelAnimation, @@ -11,6 +11,7 @@ import { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated' +import { useSafeAreaFrame } from 'react-native-safe-area-context' import { useDispatch } from 'react-redux' import { pulseAnimation } from 'src/components/buttons/utils' import { openModal } from 'src/features/modals/modalSlice' @@ -41,6 +42,9 @@ import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hoo export const NAV_BAR_HEIGHT_XS = 52 export const NAV_BAR_HEIGHT_SM = 72 +const NAV_BAR_MARGIN_SIDES = 24 +const NAV_BAR_GAP = 12 + export const SWAP_BUTTON_HEIGHT = 56 const SWAP_BUTTON_SHADOW_OFFSET = { width: 0, height: 4 } @@ -53,9 +57,29 @@ function sendSwapPressAnalyticsEvent(): void { export function NavBar(): JSX.Element { const insets = useDeviceInsets() + const { width: screenWidth } = useSafeAreaFrame() + const [isNarrow, setIsNarrow] = useState(false) + const [exploreButtonLayout, setExploreButtonLayout] = useState(null) + const [swapButtonLayout, setSwapButtonLayout] = useState(null) + const colors = useSporeColors() const isDarkMode = useIsDarkMode() + useEffect(() => { + if (isNarrow || !exploreButtonLayout?.width || !swapButtonLayout?.width) { + return + } + + // When the 2 buttons overflow, we set `isNarrow` to true and adjust the design accordingly. + // To test this, you can use an iPhone Mini set to Spanish. + setIsNarrow(exploreButtonLayout.width + swapButtonLayout.width + NAV_BAR_GAP + NAV_BAR_MARGIN_SIDES > screenWidth) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [exploreButtonLayout?.width, swapButtonLayout?.width, screenWidth]) + + const onExploreLayout = useCallback((e: LayoutChangeEvent) => setExploreButtonLayout(e.nativeEvent.layout), []) + + const onSwapLayout = useCallback((e: LayoutChangeEvent) => setSwapButtonLayout(e.nativeEvent.layout), []) + return ( <> @@ -82,14 +106,14 @@ export function NavBar(): JSX.Element { fill row alignItems="center" - gap="$spacing12" + gap={NAV_BAR_GAP} justifyContent="space-between" mb={isAndroid ? '$spacing8' : '$none'} - mx="$spacing24" + mx={NAV_BAR_MARGIN_SIDES} pointerEvents="auto" > - - + + @@ -102,9 +126,10 @@ type SwapTabBarButtonProps = { * @default 0.96 */ activeScale?: number + onSwapLayout: (event: LayoutChangeEvent) => void } -const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96 }: SwapTabBarButtonProps) { +const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96, onSwapLayout }: SwapTabBarButtonProps) { const { t } = useTranslation() const dispatch = useDispatch() const { hapticFeedback } = useHapticFeedback() @@ -138,7 +163,7 @@ const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96 }: SwapTabBarButtonP }) return ( - + void } -function ExploreTabBarButton({ activeScale = 0.98 }: ExploreTabBarButtonProps): JSX.Element { +function ExploreTabBarButton({ activeScale = 0.98, onLayout, isNarrow }: ExploreTabBarButtonProps): JSX.Element { const dispatch = useDispatch() const colors = useSporeColors() const isDarkMode = useIsDarkMode() @@ -217,6 +244,13 @@ function ExploreTabBarButton({ activeScale = 0.98 }: ExploreTabBarButtonProps): }, } + const [height, setHeight] = useState(undefined) + + const internalOnLayout = (e: LayoutChangeEvent): void => { + setHeight(e.nativeEvent.layout.height) + onLayout(e) + } + return ( - + - - {t('common.input.search')} - + {isNarrow ? undefined : ( + + {t('common.input.search')} + + )} diff --git a/apps/mobile/src/components/PriceExplorer/__snapshots__/Text.test.tsx.snap b/apps/mobile/src/components/PriceExplorer/__snapshots__/Text.test.tsx.snap index 01d12d57d67..a0e839a9f3b 100644 --- a/apps/mobile/src/components/PriceExplorer/__snapshots__/Text.test.tsx.snap +++ b/apps/mobile/src/components/PriceExplorer/__snapshots__/Text.test.tsx.snap @@ -122,7 +122,7 @@ exports[`PriceText renders without error 1`] = ` jestAnimatedStyle={ { "value": { - "color": "#CECECE", + "color": "#BFBFBF", "fontSize": 106, }, } @@ -130,7 +130,7 @@ exports[`PriceText renders without error 1`] = ` maxFontSizeMultiplier={1.2} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 106, "fontWeight": "400", diff --git a/apps/mobile/src/components/RemoveWallet/useModalContent.tsx b/apps/mobile/src/components/RemoveWallet/useModalContent.tsx index fce43abcbc5..1c4ed3bbbf4 100644 --- a/apps/mobile/src/components/RemoveWallet/useModalContent.tsx +++ b/apps/mobile/src/components/RemoveWallet/useModalContent.tsx @@ -149,5 +149,7 @@ export const useModalContent = ({ actionButtonTheme: 'secondary', } } + + return undefined }, [account, associatedAccounts, currentStep, displayName, isRemovingRecoveryPhrase, isReplacing, t]) } diff --git a/apps/mobile/src/components/Requests/RequestModal/KidSuperCheckinModal.tsx b/apps/mobile/src/components/Requests/RequestModal/KidSuperCheckinModal.tsx index 26affbc414f..4a25eb3d965 100644 --- a/apps/mobile/src/components/Requests/RequestModal/KidSuperCheckinModal.tsx +++ b/apps/mobile/src/components/Requests/RequestModal/KidSuperCheckinModal.tsx @@ -44,7 +44,7 @@ function useUniswapCafeLogo(): string | undefined { const logos = uwuLinkContractAllowlist.tokenRecipients.find((recipient) => recipient.name === 'Uniswap Cafe')?.logo if (!logos) { - return + return undefined } return isDarkMode ? logos.dark : logos.light diff --git a/apps/mobile/src/components/Requests/ScanSheet/util.ts b/apps/mobile/src/components/Requests/ScanSheet/util.ts index 8c1d4fa4da2..07beb85b4e1 100644 --- a/apps/mobile/src/components/Requests/ScanSheet/util.ts +++ b/apps/mobile/src/components/Requests/ScanSheet/util.ts @@ -98,6 +98,8 @@ export async function getSupportedURI( value: parseUwuLinkDataFromDeeplink(uri), } } + + return undefined } async function getWcUriWithCustomPrefix(uri: string, prefix: string): Promise<{ uri: string; type: URIType } | null> { @@ -160,7 +162,7 @@ export function parseScantasticParams(uri: string): ScantasticParams | undefined }, extra: { uri }, }) - return + return undefined } } @@ -186,5 +188,6 @@ export function parseScantasticParams(uri: string): ScantasticParams | undefined function: 'parseScantasticParams', }, }) + return undefined } } diff --git a/apps/mobile/src/components/Requests/Uwulink/utils.ts b/apps/mobile/src/components/Requests/Uwulink/utils.ts index 3c9339a417f..fe1a5049398 100644 --- a/apps/mobile/src/components/Requests/Uwulink/utils.ts +++ b/apps/mobile/src/components/Requests/Uwulink/utils.ts @@ -85,7 +85,7 @@ export function findAllowedTokenRecipientForUwuLink( allowlist: UwULinkAllowlist, ): UwULinkAllowlistItem | undefined { if (request.method !== UwULinkMethod.Erc20Send) { - return + return undefined } const { chainId, recipient } = request diff --git a/apps/mobile/src/components/Requests/WalletConnectModals.tsx b/apps/mobile/src/components/Requests/WalletConnectModals.tsx index 2858c67aeec..c89afaa5a4d 100644 --- a/apps/mobile/src/components/Requests/WalletConnectModals.tsx +++ b/apps/mobile/src/components/Requests/WalletConnectModals.tsx @@ -128,7 +128,7 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element { return ( } @@ -136,7 +136,7 @@ function RequestModal({ currRequest }: RequestModalProps): JSX.Element { modalName={ModalName.WCViewOnlyWarning} severity={WarningSeverity.None} title={t('walletConnect.request.warning.title')} - onCancel={onClose} + onReject={onClose} onClose={onClose} > diff --git a/apps/mobile/src/components/Settings/BiometricAuthWarningModal.tsx b/apps/mobile/src/components/Settings/BiometricAuthWarningModal.tsx index 3ad3f2ab284..ac1f302d227 100644 --- a/apps/mobile/src/components/Settings/BiometricAuthWarningModal.tsx +++ b/apps/mobile/src/components/Settings/BiometricAuthWarningModal.tsx @@ -9,7 +9,7 @@ import { isAndroid } from 'utilities/src/platform' type Props = { isOpen: boolean isTouchIdDevice: boolean - onConfirm: WarningModalProps['onConfirm'] + onConfirm: WarningModalProps['onAcknowledge'] onClose: WarningModalProps['onClose'] } @@ -23,14 +23,14 @@ export function BiometricAuthWarningModal({ isOpen, isTouchIdDevice, onConfirm, ? t('settings.setting.biometrics.warning.message.android') : t('settings.setting.biometrics.warning.message.ios', { biometricsMethod }) } - closeText={t('common.button.back')} - confirmText={t('common.button.skip')} + rejectText={t('common.button.back')} + acknowledgeText={t('common.button.skip')} isOpen={isOpen} modalName={ModalName.FaceIDWarning} severity={WarningSeverity.Low} title={t('settings.setting.biometrics.warning.title')} onClose={onClose} - onConfirm={onConfirm} + onAcknowledge={onConfirm} /> ) } diff --git a/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx b/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx index aa997e046c6..af17f53b4a5 100644 --- a/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx +++ b/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx @@ -5,21 +5,23 @@ import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState } f import { useTranslation } from 'react-i18next' import { FlatList, RefreshControl } from 'react-native' import Animated, { FadeInDown, FadeOut } from 'react-native-reanimated' -import { useDispatch } from 'react-redux' import { useAppStackNavigation } from 'src/app/navigation/types' import { TokenBalanceItemContextMenu } from 'src/components/TokenBalanceList/TokenBalanceItemContextMenu' import { useAdaptiveFooter } from 'src/components/home/hooks' import { TAB_BAR_HEIGHT, TAB_VIEW_SCROLL_THROTTLE, TabProps } from 'src/components/layout/TabHelpers' -import { openModal } from 'src/features/modals/modalSlice' import { Flex, Loader, useDeviceInsets, useSporeColors } from 'ui/src' +import { ShieldCheck } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { zIndices } from 'ui/src/theme' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' -import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' +import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { CurrencyId } from 'uniswap/src/types/currency' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { isAndroid } from 'utilities/src/platform' import { InformationBanner } from 'wallet/src/components/banners/InformationBanner' +import { InfoLinkModal } from 'wallet/src/components/modals/InfoLinkModal' import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { HiddenTokensRow } from 'wallet/src/features/portfolio/HiddenTokensRow' import { TokenBalanceItem } from 'wallet/src/features/portfolio/TokenBalanceItem' @@ -208,16 +210,15 @@ export const TokenBalanceListInner = forwardRef, T contentContainerStyle={containerProps?.contentContainerStyle} data={data} getItemLayout={getItemLayout} - initialNumToRender={20} + initialNumToRender={10} keyExtractor={keyExtractor} - maxToRenderPerBatch={20} + maxToRenderPerBatch={10} refreshControl={refreshControl} refreshing={refreshing} renderItem={renderItem} scrollEventThrottle={containerProps?.scrollEventThrottle ?? TAB_VIEW_SCROLL_THROTTLE} showsVerticalScrollIndicator={false} testID={testID} - updateCellsBatchingPeriod={10} windowSize={isFocused ? 10 : 3} onContentSizeChange={onContentSizeChange} onMomentumScrollEnd={containerProps?.onMomentumScrollEnd} @@ -246,22 +247,27 @@ const TokenBalanceItemRow = memo(function TokenBalanceItemRow({ setHiddenTokensExpanded, } = useTokenBalanceListContext() - const dispatch = useDispatch() const { t } = useTranslation() + const [isModalVisible, setModalVisible] = useState(false) - const onPressInfo = useCallback(() => { - dispatch( - openModal({ - name: ModalName.HiddenTokenInfoModal, - }), - ) - }, [dispatch]) + const handlePressToken = (): void => { + setModalVisible(true) + } + + const closeModal = (): void => { + setModalVisible(false) + } + + const handleAnalytics = (): void => { + sendAnalyticsEvent(WalletEventName.ExternalLinkOpened, { + url: uniswapUrls.helpArticleUrls.hiddenTokenInfo, + }) + } if (item === HIDDEN_TOKEN_BALANCES_ROW) { return ( - + { @@ -270,9 +276,29 @@ const TokenBalanceItemRow = memo(function TokenBalanceItemRow({ /> {hiddenTokensExpanded && ( - + )} + + + + + } + isOpen={isModalVisible} + linkText={t('common.button.learn')} + linkUrl={uniswapUrls.helpArticleUrls.hiddenTokenInfo} + name={ModalName.HiddenTokenInfoModal} + title={t('hidden.tokens.info.text.title')} + onAnalyticsEvent={handleAnalytics} + onButtonPress={closeModal} + onDismiss={closeModal} + /> ) } diff --git a/apps/mobile/src/components/TokenDetails/TokenDetailsHeader.tsx b/apps/mobile/src/components/TokenDetails/TokenDetailsHeader.tsx index a5cbc385775..27ce9171f76 100644 --- a/apps/mobile/src/components/TokenDetails/TokenDetailsHeader.tsx +++ b/apps/mobile/src/components/TokenDetails/TokenDetailsHeader.tsx @@ -27,7 +27,7 @@ export function TokenDetailsHeader({ @@ -40,7 +40,7 @@ export function TokenDetailsHeader({ testID={TestID.TokenDetailsHeaderText} variant="subheading1" > - {tokenProject?.name ?? '—'} + {token?.name ?? '—'} {/* Suppress warning icon on low warning level */} {(tokenProject?.safetyLevel === SafetyLevel.StrongWarning || diff --git a/apps/mobile/src/components/accounts/AccountList.test.tsx b/apps/mobile/src/components/accounts/AccountList.test.tsx index 85cd12f126f..5b4a988ad07 100644 --- a/apps/mobile/src/components/accounts/AccountList.test.tsx +++ b/apps/mobile/src/components/accounts/AccountList.test.tsx @@ -1,5 +1,6 @@ import { AccountList } from 'src/components/accounts/AccountList' import { cleanup, fireEvent, render, screen } from 'src/test/test-utils' +import { Locale } from 'uniswap/src/features/language/constants' import { ON_PRESS_EVENT_PAYLOAD, amounts, portfolio } from 'uniswap/src/test/fixtures' import { mockLocalizedFormatter } from 'uniswap/src/test/mocks' import { createArray, queryResolvers } from 'uniswap/src/test/utils' @@ -12,13 +13,15 @@ const { resolvers } = queryResolvers({ portfolios: () => [portfolio({ tokensTotalDenominatedValue })], }) +const formatter = mockLocalizedFormatter(Locale.EnglishUnitedStates) + describe(AccountList, () => { it('renders without error', async () => { const tree = render(, { resolvers }) expect( await screen.findByText( - mockLocalizedFormatter.formatNumberOrString({ + formatter.formatNumberOrString({ value: tokensTotalDenominatedValue.value, type: NumberType.PortfolioBalance, currencyCode: 'usd', @@ -36,7 +39,7 @@ describe(AccountList, () => { // go to success state expect( await screen.findByText( - mockLocalizedFormatter.formatNumberOrString({ + formatter.formatNumberOrString({ value: tokensTotalDenominatedValue.value, type: NumberType.PortfolioBalance, currencyCode: 'usd', diff --git a/apps/mobile/src/components/accounts/AccountList.tsx b/apps/mobile/src/components/accounts/AccountList.tsx index 7d92cd9a3fb..43dc0ebc697 100644 --- a/apps/mobile/src/components/accounts/AccountList.tsx +++ b/apps/mobile/src/components/accounts/AccountList.tsx @@ -135,6 +135,7 @@ export function AccountList({ accounts, onPress, isVisible }: AccountListProps): }, [hasSignerAccounts, hasViewOnlyAccounts, signerAccounts, viewOnlyAccounts]) const renderItem = useCallback( + // eslint-disable-next-line consistent-return ({ item }: { item: AccountListItem }) => { switch (item.type) { case AccountListItemType.SignerHeader: diff --git a/apps/mobile/src/components/accounts/__snapshots__/AccountHeader.test.tsx.snap b/apps/mobile/src/components/accounts/__snapshots__/AccountHeader.test.tsx.snap index 393ee8617ed..11a84bd0d0c 100644 --- a/apps/mobile/src/components/accounts/__snapshots__/AccountHeader.test.tsx.snap +++ b/apps/mobile/src/components/accounts/__snapshots__/AccountHeader.test.tsx.snap @@ -349,7 +349,7 @@ exports[`AccountHeader renders correctly 1`] = ` jestAnimatedStyle={ { "value": { - "color": "#CECECE", + "color": "#BFBFBF", "fontSize": 19, "fontWeight": "400", "lineHeight": 24, @@ -360,7 +360,7 @@ exports[`AccountHeader renders correctly 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -415,7 +415,7 @@ exports[`AccountHeader renders correctly 1`] = ` numberOfLines={1} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 17, "fontWeight": "400", @@ -443,7 +443,7 @@ exports[`AccountHeader renders correctly 1`] = ` "borderWidth": 0, }, { - "color": "#CECECE", + "color": "#BFBFBF", "height": 16, "width": 16, }, @@ -454,7 +454,7 @@ exports[`AccountHeader renders correctly 1`] = ` }, ] } - tintColor="#CECECE" + tintColor="#BFBFBF" vbHeight={16} vbWidth={16} > diff --git a/apps/mobile/src/components/explore/ExploreSections.tsx b/apps/mobile/src/components/explore/ExploreSections.tsx index eee3060ec56..446ffdb57eb 100644 --- a/apps/mobile/src/components/explore/ExploreSections.tsx +++ b/apps/mobile/src/components/explore/ExploreSections.tsx @@ -39,6 +39,8 @@ type ExploreSectionsProps = { listRef: React.MutableRefObject } +type GqlToken = NonNullable[0] + export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element { const { t } = useTranslation() const insets = useDeviceInsets() @@ -72,25 +74,31 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element const topTokenItems = useMemo(() => { if (!data || !data.topTokens) { - return + return undefined } // special case to replace weth with eth because the backend does not return eth data // eth will be defined only if all the required data is available // when eth data is not fully available, we do not replace weth with eth const { eth } = data + const wethAddress = getWrappedNativeAddress(UniverseChainId.Mainnet) + const isWeth = (token: GqlToken): boolean => + areAddressesEqual(token?.address, wethAddress) && token?.chain === Chain.Ethereum + + // Indentified by symbol because ETH token data comes with undefined address + const isEth = (token: GqlToken): boolean => token?.symbol === 'ETH' + const topTokens = data.topTokens + .filter((token, _, tokens) => !(isWeth(token) && tokens.some(isEth))) .map((token) => { if (!token) { - return + return undefined } - const isWeth = areAddressesEqual(token.address, wethAddress) && token?.chain === Chain.Ethereum - // manually replace weth with eth given backend only returns eth data as a proxy for eth - if (isWeth && eth) { + if (isWeth(token) && eth) { return gqlTokenToTokenItemData(eth) } @@ -224,8 +232,8 @@ function gqlTokenToTokenItemData( return null } - const { symbol, address, chain, project, market } = token - const { logoUrl, markets, name } = project + const { name, symbol, address, chain, project, market } = token + const { logoUrl, markets } = project const tokenProjectMarket = markets?.[0] const chainId = fromGraphQLChain(chain) diff --git a/apps/mobile/src/components/explore/FavoriteHeaderRow.tsx b/apps/mobile/src/components/explore/FavoriteHeaderRow.tsx index ecf7f85bdb8..4b107bcdc39 100644 --- a/apps/mobile/src/components/explore/FavoriteHeaderRow.tsx +++ b/apps/mobile/src/components/explore/FavoriteHeaderRow.tsx @@ -1,7 +1,7 @@ import { default as React } from 'react' import { useTranslation } from 'react-i18next' import { Flex, Text, TouchableArea } from 'ui/src' -import { TripleDots } from 'ui/src/components/icons' +import { Ellipsis } from 'ui/src/components/icons' import { iconSizes } from 'ui/src/theme' import { TestID } from 'uniswap/src/test/fixtures/testIDs' @@ -24,7 +24,7 @@ export function FavoriteHeaderRow({ {!isEditing ? ( - + ) : ( diff --git a/apps/mobile/src/components/explore/FavoriteTokenCard.test.tsx b/apps/mobile/src/components/explore/FavoriteTokenCard.test.tsx index cab12bedc20..421942d996e 100644 --- a/apps/mobile/src/components/explore/FavoriteTokenCard.test.tsx +++ b/apps/mobile/src/components/explore/FavoriteTokenCard.test.tsx @@ -9,8 +9,8 @@ import { SAMPLE_CURRENCY_ID_1, amount, ethToken, + tokenMarket, tokenProject, - tokenProjectMarket, } from 'uniswap/src/test/fixtures' import { queryResolvers } from 'uniswap/src/test/utils' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' @@ -31,13 +31,10 @@ jest.mock('@react-navigation/native', () => { const mockStore = configureMockStore() const favoriteToken = ethToken({ - project: tokenProject({ - markets: [ - tokenProjectMarket({ - price: amount({ value: 12345.67 }), - pricePercentChange24h: amount({ value: 4.56 }), - }), - ], + project: tokenProject(), + market: tokenMarket({ + price: amount({ value: 12345.67 }), + pricePercentChange: amount({ value: 4.56 }), }), }) @@ -98,7 +95,7 @@ describe('FavoriteTokenCard', () => { const { findByTestId } = render(, { resolvers }) const touchable = await findByTestId(`token-box-${favoriteToken.symbol}`) - await act(() => { + act(() => { fireEvent.press(touchable, ON_PRESS_EVENT_PAYLOAD) }) diff --git a/apps/mobile/src/components/explore/FavoriteTokenCard.tsx b/apps/mobile/src/components/explore/FavoriteTokenCard.tsx index c38a0f78509..7fb7b1eb6a7 100644 --- a/apps/mobile/src/components/explore/FavoriteTokenCard.tsx +++ b/apps/mobile/src/components/explore/FavoriteTokenCard.tsx @@ -63,8 +63,8 @@ function FavoriteTokenCard({ // Mirror behavior in top tokens list, use first chain the token is on for the symbol const chainId = fromGraphQLChain(token?.chain) ?? UniverseChainId.Mainnet - const price = convertFiatAmountFormatted(token?.project?.markets?.[0]?.price?.value, NumberType.FiatTokenPrice) - const pricePercentChange = token?.project?.markets?.[0]?.pricePercentChange24h?.value + const price = convertFiatAmountFormatted(token?.market?.price?.value, NumberType.FiatTokenPrice) + const pricePercentChange = token?.market?.pricePercentChange?.value const onRemove = useCallback(() => { if (currencyId) { @@ -124,7 +124,7 @@ function FavoriteTokenCard({ + } isOpen={showPopularInfo} modalName={ModalName.NetworkFeeInfo} diff --git a/apps/mobile/src/components/explore/search/SearchPopularNFTCollections.tsx b/apps/mobile/src/components/explore/search/SearchPopularNFTCollections.tsx index 3ccf24bc7d2..8bf8bbafa4a 100644 --- a/apps/mobile/src/components/explore/search/SearchPopularNFTCollections.tsx +++ b/apps/mobile/src/components/explore/search/SearchPopularNFTCollections.tsx @@ -16,7 +16,7 @@ export function SearchPopularNFTCollections(): JSX.Element { const formattedItems = useMemo(() => { if (!data?.topCollections?.edges) { - return + return undefined } const searchResults = data.topCollections.edges.map(({ node }) => gqlNFTToNFTCollectionSearchResult(node)) diff --git a/apps/mobile/src/components/explore/search/SearchPopularTokens.graphql b/apps/mobile/src/components/explore/search/SearchPopularTokens.graphql index dcf2b6e2fe2..1d525085f00 100644 --- a/apps/mobile/src/components/explore/search/SearchPopularTokens.graphql +++ b/apps/mobile/src/components/explore/search/SearchPopularTokens.graphql @@ -5,9 +5,9 @@ query SearchPopularTokens { chain symbol decimals + name project { id - name logoUrl safetyLevel } diff --git a/apps/mobile/src/components/explore/search/SearchPopularTokens.tsx b/apps/mobile/src/components/explore/search/SearchPopularTokens.tsx index 04c6a0e5f14..f3c0bccbfa9 100644 --- a/apps/mobile/src/components/explore/search/SearchPopularTokens.tsx +++ b/apps/mobile/src/components/explore/search/SearchPopularTokens.tsx @@ -13,8 +13,7 @@ function gqlTokenToTokenSearchResult(token: Maybe): TokenSearchResult return null } - const { chain, address, symbol, project, protectionInfo } = token - const { name } = project + const { name, chain, address, symbol, project, protectionInfo } = token const chainId = fromGraphQLChain(chain) if (!chainId || !symbol || !name) { return null diff --git a/apps/mobile/src/components/explore/search/SearchResultsSection.tsx b/apps/mobile/src/components/explore/search/SearchResultsSection.tsx index fd9c9392527..71a0b043a33 100644 --- a/apps/mobile/src/components/explore/search/SearchResultsSection.tsx +++ b/apps/mobile/src/components/explore/search/SearchResultsSection.tsx @@ -81,7 +81,7 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): const tokenResults = useMemo(() => { if (!searchResultsData || !searchResultsData.searchTokens) { - return + return undefined } return formatTokenSearchResults(searchResultsData.searchTokens, searchQuery) @@ -91,7 +91,7 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): const nftCollectionResults = useMemo(() => { if (!searchResultsData || !searchResultsData.nftCollections) { - return + return undefined } return formatNFTCollectionSearchResults(searchResultsData.nftCollections) diff --git a/apps/mobile/src/components/explore/search/utils.test.ts b/apps/mobile/src/components/explore/search/utils.test.ts index 61d9f8e9204..a11d3774eed 100644 --- a/apps/mobile/src/components/explore/search/utils.test.ts +++ b/apps/mobile/src/components/explore/search/utils.test.ts @@ -7,15 +7,7 @@ import { import { Chain, ExploreSearchQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { SearchResultType } from 'uniswap/src/features/search/SearchResult' -import { - amount, - ethToken, - nftCollection, - nftContract, - token, - tokenMarket, - tokenProject, -} from 'uniswap/src/test/fixtures' +import { amount, ethToken, nftCollection, nftContract, token, tokenMarket } from 'uniswap/src/test/fixtures' import { createArray } from 'uniswap/src/test/utils' type ExploreSearchResult = NonNullable @@ -62,8 +54,8 @@ describe(formatTokenSearchResults, () => { it('sorts results by best search query match', () => { const data: ExploreSearchResult['searchTokens'] = [ - ethToken({ project: tokenProject({ name: 'UniswapStartingName' }) }), - ethToken({ project: tokenProject({ name: 'Uniswap' }) }), + token({ name: 'UniswapStartingName' }), + token({ name: 'Uniswap' }), ] const result = formatTokenSearchResults(data, 'uniswap') @@ -83,7 +75,7 @@ describe(formatTokenSearchResults, () => { expect(result?.[0]?.type).toEqual(SearchResultType.Token) expect(result?.[0]?.chainId).toEqual(fromGraphQLChain(searchToken.chain)) expect(result?.[0]?.address).toEqual(searchToken.address) - expect(result?.[0]?.name).toEqual(searchToken.project?.name) + expect(result?.[0]?.name).toEqual(searchToken.name) expect(result?.[0]?.symbol).toEqual(searchToken.symbol) expect(result?.[0]?.logoUrl).toEqual(searchToken.project?.logoUrl) expect(result?.[0]?.safetyLevel).toEqual(searchToken.project?.safetyLevel) diff --git a/apps/mobile/src/components/explore/search/utils.ts b/apps/mobile/src/components/explore/search/utils.ts index 914838b43ec..c40bfb0c86d 100644 --- a/apps/mobile/src/components/explore/search/utils.ts +++ b/apps/mobile/src/components/explore/search/utils.ts @@ -20,7 +20,7 @@ export function formatTokenSearchResults( searchQuery: string, ): TokenSearchResult[] | undefined { if (!data) { - return + return undefined } // Prevent showing "duplicate" token search results for tokens that are on multiple chains @@ -31,14 +31,14 @@ export function formatTokenSearchResults( return tokensMap } - const { chain, address, symbol, project, market, protectionInfo } = token + const { name, chain, address, symbol, project, market, protectionInfo } = token const chainId = fromGraphQLChain(chain) if (!chainId || !project) { return tokensMap } - const { name, safetyLevel, logoUrl } = project + const { safetyLevel, logoUrl } = project const tokenResult: TokenSearchResult & { volume1D: number } = { type: SearchResultType.Token, @@ -87,7 +87,7 @@ export function formatNFTCollectionSearchResults( data: ExploreSearchResult['nftCollections'], ): NFTCollectionSearchResult[] | undefined { if (!data) { - return + return undefined } return data.edges.reduce((accum, { node }) => { diff --git a/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx b/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx index 4f8ee2f170b..29e6474c1c2 100644 --- a/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx +++ b/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx @@ -65,7 +65,7 @@ export function ForceUpgradeModal(): JSX.Element { return ( <> {t('forceUpgrade.description')} diff --git a/apps/mobile/src/components/home/ActivityTab.tsx b/apps/mobile/src/components/home/ActivityTab.tsx index d15a12c0cb7..d138c264500 100644 --- a/apps/mobile/src/components/home/ActivityTab.tsx +++ b/apps/mobile/src/components/home/ActivityTab.tsx @@ -80,14 +80,13 @@ export const ActivityTab = memo( // `sectionData` will be either an array of transactions or an array of loading skeletons data={sectionData} estimatedItemSize={ESTIMATED_ITEM_SIZE} - initialNumToRender={20} + initialNumToRender={10} keyExtractor={keyExtractor} - maxToRenderPerBatch={20} + maxToRenderPerBatch={10} refreshControl={refreshControl} refreshing={refreshing} renderItem={renderActivityItem} showsVerticalScrollIndicator={false} - updateCellsBatchingPeriod={10} onContentSizeChange={onContentSizeChange} onRefresh={onRefresh} onScroll={scrollHandler} diff --git a/apps/mobile/src/components/home/HomeExploreTab.tsx b/apps/mobile/src/components/home/HomeExploreTab.tsx index daa8745849e..e180d44e312 100644 --- a/apps/mobile/src/components/home/HomeExploreTab.tsx +++ b/apps/mobile/src/components/home/HomeExploreTab.tsx @@ -188,8 +188,8 @@ function gqlTokenToTokenItemData( return null } - const { symbol, address, chain, project } = token - const { logoUrl, markets, name } = project + const { name, symbol, address, chain, project } = token + const { logoUrl, markets } = project const tokenProjectMarket = markets?.[0] const chainId = fromGraphQLChain(chain) diff --git a/apps/mobile/src/components/home/introCards/OnboardingIntroCardStack.tsx b/apps/mobile/src/components/home/introCards/OnboardingIntroCardStack.tsx index d1eb6b7270f..a0aeca02bdb 100644 --- a/apps/mobile/src/components/home/introCards/OnboardingIntroCardStack.tsx +++ b/apps/mobile/src/components/home/introCards/OnboardingIntroCardStack.tsx @@ -4,20 +4,21 @@ import { FadeIn, FadeOut } from 'react-native-reanimated' import { useDispatch, useSelector } from 'react-redux' import { navigate } from 'src/app/navigation/rootNavigation' import { FundWalletModal } from 'src/components/home/introCards/FundWalletModal' -import { CardType, IntroCardProps } from 'src/components/home/introCards/IntroCard' -import { INTRO_CARD_MIN_HEIGHT, IntroCardStack, IntroCardWrapper } from 'src/components/home/introCards/IntroCardStack' import { UnitagBanner } from 'src/components/unitags/UnitagBanner' -import { useUnitagClaimHandler } from 'src/features/unitags/useUnitagClaimHandler' +import { openModal } from 'src/features/modals/modalSlice' import { Flex } from 'ui/src' import { Buy, Person, ShieldCheck, UniswapLogo } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AccountType } from 'uniswap/src/features/accounts/types' -import { ElementName, MobileEventName } from 'uniswap/src/features/telemetry/constants' +import { ElementName, MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { OnboardingCardLoggingName } from 'uniswap/src/features/telemetry/types' import { useTranslation } from 'uniswap/src/i18n' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' -import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' +import { MobileScreens, OnboardingScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile' +import { CardType, IntroCardProps } from 'wallet/src/components/introCards/IntroCard' +import { INTRO_CARD_MIN_HEIGHT, IntroCardStack } from 'wallet/src/components/introCards/IntroCardStack' +import { IntroCardWithLogging } from 'wallet/src/components/introCards/useSharedIntroCards' import { selectHasSkippedUnitagPrompt, selectHasViewedWelcomeWalletCard, @@ -25,12 +26,9 @@ import { import { setHasViewedWelcomeWalletCard } from 'wallet/src/features/behaviorHistory/slice' import { UNITAG_SUFFIX_NO_LEADING_DOT } from 'wallet/src/features/unitags/constants' import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks' +import { useUnitagClaimHandler } from 'wallet/src/features/unitags/useUnitagClaimHandler' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' -type IntroCardWithName = IntroCardWrapper & { - loggingName: OnboardingCardLoggingName -} - type OnboardingIntroCardStackProps = { onboardingRedesignHomeEnabled: boolean onboardingRedesignBackupEnabled: boolean @@ -44,17 +42,37 @@ export function OnboardingIntroCardStack({ const { t } = useTranslation() const dispatch = useDispatch() const activeAccount = useActiveAccountWithThrow() + const address = activeAccount.address const hasBackups = activeAccount.backups && activeAccount.backups.length > 0 const welcomeCardTitle = t('onboarding.home.intro.welcome.title') const hasViewedWelcomeWalletCard = useSelector(selectHasViewedWelcomeWalletCard) + const navigateToUnitagClaim = useCallback(() => { + navigate(MobileScreens.UnitagStack, { + screen: UnitagScreens.ClaimUnitag, + params: { + entryPoint: MobileScreens.Home, + address, + }, + }) + }, [address]) + + const navigateToUnitagIntro = useCallback(() => { + dispatch( + openModal({ + name: ModalName.UnitagsIntro, + initialState: { address, entryPoint: MobileScreens.Home }, + }), + ) + }, [dispatch, address]) + const hasSkippedUnitagPrompt = useSelector(selectHasSkippedUnitagPrompt) const { canClaimUnitag } = useCanActiveAddressClaimUnitag() const { handleClaim: handleUnitagClaim, handleDismiss: handleUnitagDismiss } = useUnitagClaimHandler({ - address: activeAccount.address, - entryPoint: MobileScreens.Home, analyticsEntryPoint: 'home', + navigateToClaim: navigateToUnitagClaim, + navigateToIntro: navigateToUnitagIntro, }) const [showFundModal, setShowFundModal] = useState(false) @@ -67,7 +85,7 @@ export function OnboardingIntroCardStack({ return [] } - const output: IntroCardWithName[] = [] + const output: IntroCardWithLogging[] = [] if (!hasViewedWelcomeWalletCard) { output.push({ @@ -167,11 +185,7 @@ export function OnboardingIntroCardStack({ if (cards.length) { return ( - {isLoading ? ( - - ) : ( - card.title} onSwiped={handleSwiped} /> - )} + {isLoading ? : } {showFundModal && setShowFundModal(false)} />} diff --git a/apps/mobile/src/components/icons/Favorite.tsx b/apps/mobile/src/components/icons/Favorite.tsx index 1dbf83c0b4d..881564c79da 100644 --- a/apps/mobile/src/components/icons/Favorite.tsx +++ b/apps/mobile/src/components/icons/Favorite.tsx @@ -1,8 +1,7 @@ import React, { useCallback, useEffect, useState } from 'react' import { useAnimatedStyle, useDerivedValue, withSequence, withTiming } from 'react-native-reanimated' -import { useSporeColors } from 'ui/src' -import HeartIcon from 'ui/src/assets/icons/heart.svg' -import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' +import { Flex, useSporeColors } from 'ui/src' +import { HeartWithFill } from 'ui/src/components/icons' interface FavoriteButtonProps { isFavorited: boolean @@ -38,8 +37,8 @@ export const Favorite = ({ isFavorited, size }: FavoriteButtonProps): JSX.Elemen const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale]) return ( - - - + + + ) } diff --git a/apps/mobile/src/components/mnemonic/SeedPhraseDisplay.tsx b/apps/mobile/src/components/mnemonic/SeedPhraseDisplay.tsx index 8d51760921d..20302b6a373 100644 --- a/apps/mobile/src/components/mnemonic/SeedPhraseDisplay.tsx +++ b/apps/mobile/src/components/mnemonic/SeedPhraseDisplay.tsx @@ -73,29 +73,29 @@ export function SeedPhraseDisplay({ mnemonicId, onDismiss, walletNeedsRestore }: { + onReject={(): void => { setShowSeedPhraseViewWarningModal(false) if (!showSeedPhrase) { onDismiss?.() } }} - onConfirm={onConfirmWarning} + onAcknowledge={onConfirmWarning} /> )} setShowScreenShotWarningModal(false)} + onAcknowledge={(): void => setShowScreenShotWarningModal(false)} /> ) diff --git a/apps/mobile/src/components/sortableGrid/contexts/LayoutContextProvider.tsx b/apps/mobile/src/components/sortableGrid/contexts/LayoutContextProvider.tsx index be649641a98..c9a0aa06a7b 100644 --- a/apps/mobile/src/components/sortableGrid/contexts/LayoutContextProvider.tsx +++ b/apps/mobile/src/components/sortableGrid/contexts/LayoutContextProvider.tsx @@ -150,6 +150,8 @@ export function LayoutContextProvider({ } rowOffsets.value = offsets } + + return undefined }, [numColumns], ) diff --git a/apps/mobile/src/components/text/__snapshots__/DecimalNumber.test.tsx.snap b/apps/mobile/src/components/text/__snapshots__/DecimalNumber.test.tsx.snap index 5c375be5301..dac06fab0f7 100644 --- a/apps/mobile/src/components/text/__snapshots__/DecimalNumber.test.tsx.snap +++ b/apps/mobile/src/components/text/__snapshots__/DecimalNumber.test.tsx.snap @@ -21,7 +21,7 @@ exports[`renders a DecimalNumber 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -57,7 +57,7 @@ exports[`renders a DecimalNumber without a comma separator 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", diff --git a/apps/mobile/src/components/text/__snapshots__/TextWithFuseMatches.test.tsx.snap b/apps/mobile/src/components/text/__snapshots__/TextWithFuseMatches.test.tsx.snap index 8c519688ecf..0eb50c2cfbd 100644 --- a/apps/mobile/src/components/text/__snapshots__/TextWithFuseMatches.test.tsx.snap +++ b/apps/mobile/src/components/text/__snapshots__/TextWithFuseMatches.test.tsx.snap @@ -46,7 +46,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -62,7 +62,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -158,7 +158,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -174,7 +174,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -190,7 +190,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -206,7 +206,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -222,7 +222,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -238,7 +238,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -254,7 +254,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -270,7 +270,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -286,7 +286,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -302,7 +302,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -318,7 +318,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -334,7 +334,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", @@ -350,7 +350,7 @@ exports[`renders text with few matches 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 19, "fontWeight": "400", diff --git a/apps/mobile/src/components/tooltip/TooltipButton.tsx b/apps/mobile/src/components/tooltip/TooltipButton.tsx index 206b2042e1d..f06639e4ef4 100644 --- a/apps/mobile/src/components/tooltip/TooltipButton.tsx +++ b/apps/mobile/src/components/tooltip/TooltipButton.tsx @@ -50,7 +50,7 @@ export function TooltipInfoButton({ { if (!response.didCancel && !response.errorCode && response.assets) { return response.assets[0]?.uri } + return undefined } export function useAvatarSelectionHandler({ diff --git a/apps/mobile/src/components/unitags/UnitagBanner.tsx b/apps/mobile/src/components/unitags/UnitagBanner.tsx index d8332b6cb92..65ea0f48266 100644 --- a/apps/mobile/src/components/unitags/UnitagBanner.tsx +++ b/apps/mobile/src/components/unitags/UnitagBanner.tsx @@ -1,14 +1,18 @@ -import React from 'react' +import React, { useCallback } from 'react' import { Trans, useTranslation } from 'react-i18next' -import { useUnitagClaimHandler } from 'src/features/unitags/useUnitagClaimHandler' +import { useDispatch } from 'react-redux' +import { navigate } from 'src/app/navigation/rootNavigation' +import { openModal } from 'src/features/modals/modalSlice' import { Flex, Image, Text, TouchableArea, TouchableAreaProps, useIsDarkMode, useIsShortMobileDevice } from 'ui/src' import { UNITAGS_BANNER_VERTICAL_DARK, UNITAGS_BANNER_VERTICAL_LIGHT } from 'ui/src/assets' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { iconSizes } from 'ui/src/theme' +import { ModalName } from 'uniswap/src/features/telemetry/constants' import { TestID } from 'uniswap/src/test/fixtures/testIDs' -import { MobileScreens } from 'uniswap/src/types/screens/mobile' +import { MobileScreens, UnitagScreens } from 'uniswap/src/types/screens/mobile' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { UNITAG_SUFFIX_NO_LEADING_DOT } from 'wallet/src/features/unitags/constants' +import { useUnitagClaimHandler } from 'wallet/src/features/unitags/useUnitagClaimHandler' const IMAGE_ASPECT_RATIO = 0.42 const IMAGE_SCREEN_WIDTH_PROPORTION = 0.18 @@ -27,6 +31,7 @@ export function UnitagBanner({ const { fullWidth } = useDeviceDimensions() const isDarkMode = useIsDarkMode() const isShortDevice = useIsShortMobileDevice() + const dispatch = useDispatch() const imageWidth = compact ? COMPACT_IMAGE_SCREEN_WIDTH_PROPORTION * fullWidth @@ -34,10 +39,29 @@ export function UnitagBanner({ const imageHeight = imageWidth / IMAGE_ASPECT_RATIO const analyticsEntryPoint = entryPoint === MobileScreens.Home ? 'home' : 'settings' + const navigateToClaim = useCallback(() => { + navigate(MobileScreens.UnitagStack, { + screen: UnitagScreens.ClaimUnitag, + params: { + entryPoint: MobileScreens.Home, + address, + }, + }) + }, [address]) + + const navigateToIntro = useCallback(() => { + dispatch( + openModal({ + name: ModalName.UnitagsIntro, + initialState: { address, entryPoint: MobileScreens.Home }, + }), + ) + }, [dispatch, address]) + const { handleClaim, handleDismiss } = useUnitagClaimHandler({ - address, - entryPoint, analyticsEntryPoint, + navigateToClaim, + navigateToIntro, }) const onPressClaimNow = (): void => { diff --git a/apps/mobile/src/features/CloudBackup/CloudBackupProcessingAnimation.tsx b/apps/mobile/src/features/CloudBackup/CloudBackupProcessingAnimation.tsx index d0c7b4755f5..542eeb99001 100644 --- a/apps/mobile/src/features/CloudBackup/CloudBackupProcessingAnimation.tsx +++ b/apps/mobile/src/features/CloudBackup/CloudBackupProcessingAnimation.tsx @@ -65,6 +65,7 @@ export function CloudBackupProcessingAnimation({ const timer = setTimeout(onBackupComplete, ONE_SECOND_MS) return () => clearTimeout(timer) } + return undefined }, [account?.backups, onBackupComplete]) // Handle backup to Cloud when screen appears diff --git a/apps/mobile/src/features/appRating/saga.ts b/apps/mobile/src/features/appRating/saga.ts index 00f767f44b2..83fb6d1603a 100644 --- a/apps/mobile/src/features/appRating/saga.ts +++ b/apps/mobile/src/features/appRating/saga.ts @@ -38,7 +38,7 @@ const getStoreReview = async () => { } catch (error) { const message = error instanceof Error ? error.message : 'Store Review import error' logger.warn('appRating/saga.ts', 'getStoreReview', message) - return + return undefined } } diff --git a/apps/mobile/src/features/fiatOnRamp/FiatOnRampAmountSection.tsx b/apps/mobile/src/features/fiatOnRamp/FiatOnRampAmountSection.tsx index 31a7552160c..d3bc230c6b4 100644 --- a/apps/mobile/src/features/fiatOnRamp/FiatOnRampAmountSection.tsx +++ b/apps/mobile/src/features/fiatOnRamp/FiatOnRampAmountSection.tsx @@ -38,13 +38,11 @@ const PREDEFINED_AMOUNTS = [100, 300, 1000] type OnChangeAmount = (amount: string) => void -function OnRampError({ errorText, color }: { errorText: string; color: ColorTokens }): JSX.Element { +function OnRampError({ errorText, color }: { errorText?: string; color: ColorTokens }): JSX.Element { return ( - - - {errorText} - - + + {errorText} + ) } @@ -176,9 +174,13 @@ export const FiatOnRampAmountSection = forwardRef - - {debouncedErrorText} - + {notAvailableInThisRegion ? ( + + ) : debouncedErrorText ? ( + + ) : !appFiatCurrencySupported ? ( + + ) : null} @@ -253,11 +255,6 @@ export const FiatOnRampAmountSection = forwardRef )} - {notAvailableInThisRegion ? ( - - ) : !appFiatCurrencySupported ? ( - - ) : null} ) }, diff --git a/apps/mobile/src/features/import/GenericImportForm.tsx b/apps/mobile/src/features/import/GenericImportForm.tsx index 3febc374592..ae1e030a98e 100644 --- a/apps/mobile/src/features/import/GenericImportForm.tsx +++ b/apps/mobile/src/features/import/GenericImportForm.tsx @@ -152,7 +152,13 @@ export function GenericImportForm({ py="$spacing16" top={0} > - + {placeholderLabel} diff --git a/apps/mobile/src/features/import/InputWithSuffix.android.tsx b/apps/mobile/src/features/import/InputWithSuffix.android.tsx index 235c46a5565..bb0185db220 100644 --- a/apps/mobile/src/features/import/InputWithSuffix.android.tsx +++ b/apps/mobile/src/features/import/InputWithSuffix.android.tsx @@ -62,7 +62,6 @@ export default function InputWithSuffix({ px="$none" py="$none" scrollEnabled={false} - textAlignVertical="bottom" value={inputSuffix} /> ) : null @@ -86,7 +85,6 @@ export default function InputWithSuffix({ autoCapitalize="none" backgroundColor="$transparent" color="$neutral1" - flexShrink={1} fontSize={inputFontSize} lineHeight={inputFontSize} maxFontSizeMultiplier={inputMaxFontSizeMultiplier} @@ -98,8 +96,8 @@ export default function InputWithSuffix({ spellCheck={false} testID={TestID.ImportAccountInput} textAlign={isInputEmpty ? 'left' : textInputAlignment} - textAlignVertical={isInputEmpty ? 'center' : 'bottom'} value={value} + verticalAlign="center" onLayout={measureInputWidth} {...inputProps} /> diff --git a/apps/mobile/src/features/import/InputWithSuffix.ios.tsx b/apps/mobile/src/features/import/InputWithSuffix.ios.tsx index 88cec2ba5da..a23af8ee202 100644 --- a/apps/mobile/src/features/import/InputWithSuffix.ios.tsx +++ b/apps/mobile/src/features/import/InputWithSuffix.ios.tsx @@ -41,7 +41,6 @@ export default function InputWithSuffix({ spellCheck={false} testID={TestID.ImportAccountInput} textAlign={isInputEmpty ? 'left' : textInputAlignment} - textAlignVertical="bottom" value={value} {...inputProps} /> @@ -57,7 +56,6 @@ export default function InputWithSuffix({ px="$none" py="$none" scrollEnabled={false} - textAlignVertical="bottom" value={inputSuffix} /> ) : null} diff --git a/apps/mobile/src/features/import/__snapshots__/GenericImportForm.test.tsx.snap b/apps/mobile/src/features/import/__snapshots__/GenericImportForm.test.tsx.snap index 66580e8076e..d3d63dae014 100644 --- a/apps/mobile/src/features/import/__snapshots__/GenericImportForm.test.tsx.snap +++ b/apps/mobile/src/features/import/__snapshots__/GenericImportForm.test.tsx.snap @@ -62,10 +62,10 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` onChangeText={[Function]} onFocus={[Function]} onSubmitEditing={[Function]} - placeholderTextColor="#CECECE" + placeholderTextColor="#BFBFBF" returnKeyType="done" scrollEnabled={false} - selectionColor="#CECECE" + selectionColor="#BFBFBF" spellCheck={false} style={ { @@ -99,7 +99,6 @@ exports[`GenericImportForm renders a placeholder when there is no value 1`] = ` } } testID="import-account-input" - textAlignVertical="bottom" /> seed phrase diff --git a/apps/mobile/src/features/modals/ModalsState.ts b/apps/mobile/src/features/modals/ModalsState.ts index 78f2e8bf558..b13b4d7dabe 100644 --- a/apps/mobile/src/features/modals/ModalsState.ts +++ b/apps/mobile/src/features/modals/ModalsState.ts @@ -18,7 +18,6 @@ export interface ModalsState { [ModalName.BackupReminder]: AppModalState [ModalName.BackupReminderWarning]: AppModalState [ModalName.KoreaCexTransferInfoModal]: AppModalState - [ModalName.HiddenTokenInfoModal]: AppModalState [ModalName.ExchangeTransferModal]: AppModalState<{ serviceProvider: FORServiceProvider }> diff --git a/apps/mobile/src/features/modals/modalSlice.ts b/apps/mobile/src/features/modals/modalSlice.ts index aca01e5cad5..28790687edc 100644 --- a/apps/mobile/src/features/modals/modalSlice.ts +++ b/apps/mobile/src/features/modals/modalSlice.ts @@ -17,11 +17,6 @@ type AccountSwitcherModalParams = { initialState?: undefined } -type HiddenTokenInfoModalParams = { - name: typeof ModalName.HiddenTokenInfoModal - initialState?: undefined -} - type KoreaCexTransferInfoModalParams = { name: typeof ModalName.KoreaCexTransferInfoModal initialState?: undefined @@ -105,7 +100,6 @@ export type OpenModalParams = | BackupReminderParams | BackupWarningParams | KoreaCexTransferInfoModalParams - | HiddenTokenInfoModalParams | ExchangeTransferModalParams | ExperimentsModalParams | ExploreModalParams diff --git a/apps/mobile/src/features/nfts/item/traits.tsx b/apps/mobile/src/features/nfts/item/traits.tsx index 6fa510fa022..d093d28545a 100644 --- a/apps/mobile/src/features/nfts/item/traits.tsx +++ b/apps/mobile/src/features/nfts/item/traits.tsx @@ -8,7 +8,7 @@ import { NftAssetTrait } from 'uniswap/src/data/graphql/uniswap-data-api/__gener const formatTraitValue = (trait: NftAssetTrait): string | undefined => { if (!trait.value) { - return + return undefined } if (trait.name?.toLowerCase().split(' ').includes('date')) { diff --git a/apps/mobile/src/features/onboarding/BackupSpeedBumpModal.tsx b/apps/mobile/src/features/onboarding/BackupSpeedBumpModal.tsx index 9a3dcae0782..22fd6830026 100644 --- a/apps/mobile/src/features/onboarding/BackupSpeedBumpModal.tsx +++ b/apps/mobile/src/features/onboarding/BackupSpeedBumpModal.tsx @@ -19,6 +19,7 @@ export function BackupSpeedBumpModal({ backupType, onContinue, onClose }: Backup const { t } = useTranslation() const [checked, setChecked] = useState(false) + // eslint-disable-next-line consistent-return const { preview, title, description, disclaimer } = useMemo(() => { switch (backupType) { case BackupType.Cloud: diff --git a/apps/mobile/src/features/openai/OpenAIContext.tsx b/apps/mobile/src/features/openai/OpenAIContext.tsx index 961e1005102..c8bb2d63d7e 100644 --- a/apps/mobile/src/features/openai/OpenAIContext.tsx +++ b/apps/mobile/src/features/openai/OpenAIContext.tsx @@ -71,7 +71,7 @@ async function handleRunStatus( if (run.status === 'completed') { processMessages() } else if (run.status === 'requires_action') { - return await handleRequiresAction(threadId, run, processMessages, toolsMap) + await handleRequiresAction(threadId, run, processMessages, toolsMap) } else { logger.debug('OpenAIContext.tsx', 'handleRunStatus', `Run did not complete: ${run.id}`) } @@ -400,6 +400,7 @@ function _OpenAIContextProvider({ children }: { children: React.ReactNode }): JS }) return listener.remove } + return undefined }, [mainThread, sendMessage]) const value = { diff --git a/apps/mobile/src/features/send/SendFormScreen.tsx b/apps/mobile/src/features/send/SendFormScreen.tsx index 211291f8cef..0ef051d6f97 100644 --- a/apps/mobile/src/features/send/SendFormScreen.tsx +++ b/apps/mobile/src/features/send/SendFormScreen.tsx @@ -21,6 +21,7 @@ import { TransactionModalInnerContainer, } from 'uniswap/src/features/transactions/TransactionModal/TransactionModal' import { useTransactionModalContext } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' +import { WalletChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' @@ -69,7 +70,7 @@ function SendFormScreenContent({ hideContent }: { hideContent: boolean }): JSX.E > } isOpen={showViewOnlyModal} modalName={ModalName.SwapWarning} severity={WarningSeverity.Low} title={t('send.warning.viewOnly.title')} onClose={(): void => setShowViewOnlyModal(false)} - onConfirm={(): void => setShowViewOnlyModal(false)} + onAcknowledge={(): void => setShowViewOnlyModal(false)} /> diff --git a/apps/mobile/src/features/send/SendRecipientSelectFullScreen.tsx b/apps/mobile/src/features/send/SendRecipientSelectFullScreen.tsx index 61d38bf37f1..3b2eccde9d8 100644 --- a/apps/mobile/src/features/send/SendRecipientSelectFullScreen.tsx +++ b/apps/mobile/src/features/send/SendRecipientSelectFullScreen.tsx @@ -3,6 +3,7 @@ import { RecipientSelect } from 'src/components/RecipientSelect/RecipientSelect' import { SEND_CONTENT_RENDER_DELAY_MS } from 'src/features/send/constants' import { TransactionModalInnerContainer } from 'uniswap/src/features/transactions/TransactionModal/TransactionModal' import { useTransactionModalContext } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' +import { WalletChainId } from 'uniswap/src/types/chains' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' // We add a short hardcoded delay to allow the sheet to animate quickly both on first render and when going back from Review -> Form. @@ -34,7 +35,7 @@ function SendRecipientSelectFullScreenContent({ hideContent }: { hideContent: bo {!hideContent && ( } isOpen={showWarningModal} modalName={ModalName.SendWarning} severity={transferWarning.severity} title={transferWarning.title} onClose={(): void => setShowWarningModal(false)} - onConfirm={(): void => setShowWarningModal(false)} + onAcknowledge={(): void => setShowWarningModal(false)} /> )} diff --git a/apps/mobile/src/features/unitags/ChooseProfilePictureScreen.tsx b/apps/mobile/src/features/unitags/ChooseProfilePictureScreen.tsx index 420ded255fb..e16c5e21a65 100644 --- a/apps/mobile/src/features/unitags/ChooseProfilePictureScreen.tsx +++ b/apps/mobile/src/features/unitags/ChooseProfilePictureScreen.tsx @@ -89,6 +89,7 @@ export function ChooseProfilePictureScreen({ entryPoint: OnboardingEntryPoint.FreshInstallOrReplace, }, }) + return Promise.resolve() } else { return attemptClaimUnitag() } diff --git a/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx b/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx index 31866670d16..acfe9683ff3 100644 --- a/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx +++ b/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx @@ -14,7 +14,7 @@ import { DeleteUnitagModal } from 'src/components/unitags/DeleteUnitagModal' import { UnitagProfilePicture } from 'src/components/unitags/UnitagProfilePicture' import { HeaderRadial, solidHeaderProps } from 'src/features/externalProfile/ProfileHeader' import { Button, Flex, LinearGradient, ScrollView, Text, getUniconColors, useIsDarkMode, useSporeColors } from 'ui/src' -import { Pen, TripleDots } from 'ui/src/components/icons' +import { Ellipsis, Pen } from 'ui/src/components/icons' import { borderRadii, fonts, iconSizes, imageSizes, spacing } from 'ui/src/theme' import { useExtractedColors } from 'ui/src/utils/colors' import { TextInput } from 'uniswap/src/components/input/TextInput' @@ -271,7 +271,7 @@ export function EditUnitagProfileScreen({ route }: UnitagStackScreenProp - + ) : undefined diff --git a/apps/mobile/src/features/walletConnect/selectors.ts b/apps/mobile/src/features/walletConnect/selectors.ts index 3e97e6426d8..49ce9484be8 100644 --- a/apps/mobile/src/features/walletConnect/selectors.ts +++ b/apps/mobile/src/features/walletConnect/selectors.ts @@ -10,12 +10,12 @@ export const selectSessions = (address: Maybe) => (state: MobileState): WalletConnectSession[] | undefined => { if (!address) { - return + return undefined } const wcAccount = state.walletConnect.byAccount[address] if (!wcAccount) { - return + return undefined } return Object.values(wcAccount.sessions) @@ -27,12 +27,12 @@ export const makeSelectSessions = (): Selector) => address, (sessionsByAccount, address) => { if (!address) { - return + return undefined } const wcAccount = sessionsByAccount[address] if (!wcAccount) { - return + return undefined } return Object.values(wcAccount.sessions) diff --git a/apps/mobile/src/features/walletConnect/utils.ts b/apps/mobile/src/features/walletConnect/utils.ts index ceee1373455..44003a0dfa4 100644 --- a/apps/mobile/src/features/walletConnect/utils.ts +++ b/apps/mobile/src/features/walletConnect/utils.ts @@ -43,7 +43,7 @@ export const getSessionNamespaces = ( */ export const getSupportedWalletConnectChains = (chains?: string[]): WalletChainId[] | undefined => { if (!chains) { - return + return undefined } return chains.map((chain) => getChainIdFromEIP155String(chain)).filter((c): c is WalletChainId => Boolean(c)) @@ -166,6 +166,7 @@ export const parseTransactionRequest = ( * `signTypedData` params are ordered as [account, message] * See https://docs.walletconnect.com/2.0/advanced/rpc-reference/ethereum-rpc#personal_sign */ +// eslint-disable-next-line consistent-return export function getAddressAndMessageToSign( ethMethod: EthSignMethod, params: Web3WalletTypes.SessionRequest['params']['request']['params'], diff --git a/apps/mobile/src/screens/ExternalProfileScreen.tsx b/apps/mobile/src/screens/ExternalProfileScreen.tsx index 9c34dcfd34d..bacaffe39b7 100644 --- a/apps/mobile/src/screens/ExternalProfileScreen.tsx +++ b/apps/mobile/src/screens/ExternalProfileScreen.tsx @@ -117,7 +117,7 @@ export function ExternalProfileScreen({ {...sceneProps} indicatorStyle={TAB_STYLES.activeTabIndicator} navigationState={{ index: tabIndex, routes: tabs }} - pressColor={colors.surface3.val} // Android only + pressColor="transparent" // Android only renderLabel={({ route, focused }): JSX.Element => ( )} diff --git a/apps/mobile/src/screens/FiatOnRampScreen.tsx b/apps/mobile/src/screens/FiatOnRampScreen.tsx index f6bba12c962..078e53e275b 100644 --- a/apps/mobile/src/screens/FiatOnRampScreen.tsx +++ b/apps/mobile/src/screens/FiatOnRampScreen.tsx @@ -59,6 +59,7 @@ import { CurrencyField } from 'uniswap/src/types/currency' import { FiatOnRampScreens } from 'uniswap/src/types/screens/mobile' import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' import { truncateToMaxDecimals } from 'utilities/src/format/truncateToMaxDecimals' +import { isIOS } from 'utilities/src/platform' import { usePrevious } from 'utilities/src/react/hooks' import { DEFAULT_DELAY, useDebounce } from 'utilities/src/time/timing' import { useWalletNavigation } from 'wallet/src/contexts/WalletNavigationContext' @@ -237,9 +238,6 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element { decimalPadRef.current?.updateDisabledKeys() } - // we only show loading when there are no errors and quote value is not empty - const buttonDisabled = selectTokenLoading || !!quotesError || !selectedQuote?.destinationAmount - const onContinue = (): void => { if (quotes && quoteCurrency?.currencyInfo?.currency) { setBaseCurrencyInfo(meldSupportedFiatCurrency) @@ -338,6 +336,10 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element { }) } + // we only show loading when there are no errors and quote value is not empty + const buttonDisabled = + notAvailableInThisRegion || selectTokenLoading || !!quotesError || !selectedQuote?.destinationAmount + return ( @@ -404,7 +406,8 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element { gap={isShortMobileDevice ? 0 : '$spacing8'} left={0} opacity={decimalPadReady ? 1 : 0} - pb={isShortMobileDevice ? '$spacing4' : '$spacing24'} + // android devices require more bottom padding + pb={isShortMobileDevice && isIOS ? '$spacing4' : '$spacing24'} position="absolute" px="$spacing24" right={0} diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index 95337cc68eb..bd3a16d2fb4 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -550,7 +550,7 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. {...sceneProps} indicatorStyle={TAB_STYLES.activeTabIndicator} navigationState={{ index: tabIndex, routes }} - pressColor={colors.surface3.val} // Android only + pressColor="transparent" // Android only renderLabel={renderTabLabel} style={[ TAB_STYLES.tabBar, diff --git a/apps/mobile/src/screens/Import/OnDeviceRecoveryScreen.tsx b/apps/mobile/src/screens/Import/OnDeviceRecoveryScreen.tsx index b24f5deb3d5..4e7e29b2f44 100644 --- a/apps/mobile/src/screens/Import/OnDeviceRecoveryScreen.tsx +++ b/apps/mobile/src/screens/Import/OnDeviceRecoveryScreen.tsx @@ -75,6 +75,7 @@ export function OnDeviceRecoveryScreen({ if (mnemonicId !== selectedMnemonicId) { return Keyring.removeMnemonic(mnemonicId) } + return Promise.resolve() }), ) } @@ -86,6 +87,7 @@ export function OnDeviceRecoveryScreen({ if (!selectedRecoveryWalletInfos.find((walletInfo) => walletInfo.address === address)) { return Keyring.removePrivateKey(address) } + return Promise.resolve() }), ) } @@ -247,15 +249,15 @@ export function OnDeviceRecoveryScreen({ } isOpen={showConfirmationModal} modalName={ModalName.OnDeviceRecoveryConfirmation} severity={WarningSeverity.None} title={t('onboarding.import.onDeviceRecovery.warning.title')} onClose={onPressClose} - onConfirm={onPressConfirm} + onAcknowledge={onPressConfirm} /> diff --git a/apps/mobile/src/screens/Import/RestoreCloudBackupLoadingScreen.tsx b/apps/mobile/src/screens/Import/RestoreCloudBackupLoadingScreen.tsx index 1e61f6696ee..89a9972ca64 100644 --- a/apps/mobile/src/screens/Import/RestoreCloudBackupLoadingScreen.tsx +++ b/apps/mobile/src/screens/Import/RestoreCloudBackupLoadingScreen.tsx @@ -70,7 +70,7 @@ export function RestoreCloudBackupLoadingScreen({ navigation, route: { params } */ useEffect(() => { if (!isLoading) { - return + return undefined } const timer = setTimeout( () => { diff --git a/apps/mobile/src/screens/Import/RestoreCloudBackupPasswordScreen.tsx b/apps/mobile/src/screens/Import/RestoreCloudBackupPasswordScreen.tsx index 636d271199e..0e07be9c328 100644 --- a/apps/mobile/src/screens/Import/RestoreCloudBackupPasswordScreen.tsx +++ b/apps/mobile/src/screens/Import/RestoreCloudBackupPasswordScreen.tsx @@ -101,6 +101,7 @@ export function RestoreCloudBackupPasswordScreen({ navigation, route: { params } return () => clearTimeout(timer) } + return undefined }, [isLockedOut, lockoutMessage, remainingLockoutTime, dispatch]), ) diff --git a/apps/mobile/src/screens/Import/__snapshots__/RestoreCloudBackupPasswordScreen.test.tsx.snap b/apps/mobile/src/screens/Import/__snapshots__/RestoreCloudBackupPasswordScreen.test.tsx.snap index 49fd90fa8d9..243228dde41 100644 --- a/apps/mobile/src/screens/Import/__snapshots__/RestoreCloudBackupPasswordScreen.test.tsx.snap +++ b/apps/mobile/src/screens/Import/__snapshots__/RestoreCloudBackupPasswordScreen.test.tsx.snap @@ -274,10 +274,10 @@ exports[`RestoreCloudBackupPasswordScreen renders correctly 1`] = ` onFocus={[Function]} onSubmitEditing={[Function]} placeholder="Enter password" - placeholderTextColor="#CECECE" + placeholderTextColor="#BFBFBF" returnKeyType="done" secureTextEntry={true} - selectionColor="#CECECE" + selectionColor="#BFBFBF" style={ { "backgroundColor": "transparent", diff --git a/apps/mobile/src/screens/NFTCollectionScreen.tsx b/apps/mobile/src/screens/NFTCollectionScreen.tsx index 027403ec605..d7592972e7e 100644 --- a/apps/mobile/src/screens/NFTCollectionScreen.tsx +++ b/apps/mobile/src/screens/NFTCollectionScreen.tsx @@ -43,7 +43,7 @@ const keyExtractor = (item: NFTItem | string, index: number): string => function gqlNFTAssetToNFTItem(data: NftCollectionScreenQuery | undefined): NFTItem[] | undefined { const items = data?.nftAssets?.edges?.flatMap((item) => item.node) if (!items) { - return + return undefined } return items.map((item): NFTItem => { diff --git a/apps/mobile/src/screens/Onboarding/ManualBackupScreen.tsx b/apps/mobile/src/screens/Onboarding/ManualBackupScreen.tsx index 4974a571b9f..c8efde6818c 100644 --- a/apps/mobile/src/screens/Onboarding/ManualBackupScreen.tsx +++ b/apps/mobile/src/screens/Onboarding/ManualBackupScreen.tsx @@ -89,7 +89,7 @@ export function ManualBackupScreen({ navigation, route: { params } }: Props): JS useEffect(() => { if (view !== View.SeedPhrase) { - return + return undefined } const listener = addScreenshotListener(() => setShowScreenShotWarningModal(true)) @@ -135,11 +135,11 @@ export function ManualBackupScreen({ navigation, route: { params } }: Props): JS > setShowScreenShotWarningModal(false)} + onAcknowledge={(): void => setShowScreenShotWarningModal(false)} /> diff --git a/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap b/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap index 077ed7331a8..8937e59a106 100644 --- a/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap +++ b/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap @@ -737,7 +737,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` "borderWidth": 0, }, { - "color": "#CECECE", + "color": "#BFBFBF", "height": 20, "width": 20, }, @@ -748,7 +748,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` }, ] } - tintColor="#CECECE" + tintColor="#BFBFBF" vbHeight={24} vbWidth={24} > @@ -782,7 +782,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 17, "fontWeight": "400", @@ -1539,7 +1539,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` "borderWidth": 0, }, { - "color": "#CECECE", + "color": "#BFBFBF", "height": 20, "width": 20, }, @@ -1550,7 +1550,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` }, ] } - tintColor="#CECECE" + tintColor="#BFBFBF" vbHeight={24} vbWidth={24} > @@ -1584,7 +1584,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#CECECE", + "color": "#BFBFBF", "fontFamily": "Basel Grotesk", "fontSize": 17, "fontWeight": "400", diff --git a/apps/mobile/src/screens/SettingsCloudBackupStatus.tsx b/apps/mobile/src/screens/SettingsCloudBackupStatus.tsx index 45876c184a3..b625ba0c4a8 100644 --- a/apps/mobile/src/screens/SettingsCloudBackupStatus.tsx +++ b/apps/mobile/src/screens/SettingsCloudBackupStatus.tsx @@ -135,15 +135,15 @@ export function SettingsCloudBackupStatus({ caption={t('settings.setting.backup.delete.warning', { cloudProviderName: getCloudProviderName(), })} - closeText={t('common.button.close')} - confirmText={t('common.button.delete')} + rejectText={t('common.button.close')} + acknowledgeText={t('common.button.delete')} isOpen={showBackupDeleteWarning} modalName={ModalName.ViewSeedPhraseWarning} title={t('settings.setting.backup.delete.confirm.title')} onClose={(): void => { setShowBackupDeleteWarning(false) }} - onConfirm={onConfirmDeleteBackup} + onAcknowledge={onConfirmDeleteBackup} > {associatedAccounts.length > 1 && ( diff --git a/apps/mobile/src/screens/TokenDetailsScreen.tsx b/apps/mobile/src/screens/TokenDetailsScreen.tsx index 07058da59e7..16c07e9ea64 100644 --- a/apps/mobile/src/screens/TokenDetailsScreen.tsx +++ b/apps/mobile/src/screens/TokenDetailsScreen.tsx @@ -34,6 +34,8 @@ import { TokenDetailsScreenQuery, useTokenDetailsScreenQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { AssetType } from 'uniswap/src/entities/assets' +import { useSwappableTokenWithHighestBalance } from 'uniswap/src/features/bridging/hooks/useSwappableTokenWithHighestBalance' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' @@ -141,9 +143,9 @@ export function TokenDetailsScreen({ route }: AppStackScreenProp ({ address: currencyIdToAddress(_currencyId), chain: currencyIdToChain(_currencyId), - currencyName: data?.token?.project?.name, + currencyName: data?.token?.name, }), - [_currencyId, data?.token?.project?.name], + [_currencyId, data?.token?.name], ) return ( @@ -186,7 +188,7 @@ function TokenDetails({ const token = data?.token const tokenLogoUrl = token?.project?.logoUrl - const tokenSymbol = token?.project?.name + const tokenSymbol = token?.name const currencyInfo = useCurrencyInfo(_currencyId) @@ -236,6 +238,12 @@ function TokenDetails({ const safetyLevel = token?.project?.safetyLevel + const swappableTokenWithHighestBalance = useSwappableTokenWithHighestBalance({ + currencyAddress, + currencyChainId, + otherChainBalances, + }) + const onPressSend = useCallback(() => { // Do not show warning modal speedbump if user is trying to send tokens they own navigateToSend({ currencyAddress, chainId: currencyChainId }) @@ -249,12 +257,38 @@ function TokenDetails({ } else if (safetyLevel !== SafetyLevel.Verified && !tokenWarningDismissed) { setActiveTransactionType(currencyField) setShowWarningModal(true) + } else if (swappableTokenWithHighestBalance && currencyField === CurrencyField.OUTPUT) { + // When clicking "Buy", if the user has a balance in another chain, we prepopulate the input token with that token. + setActiveTransactionType(undefined) + navigateToSwapFlow({ + initialState: { + exactCurrencyField: CurrencyField.INPUT, + input: { + address: currencyIdToAddress(swappableTokenWithHighestBalance.currencyInfo.currencyId), + chainId: swappableTokenWithHighestBalance.currencyInfo.currency.chainId, + type: AssetType.Currency, + }, + output: { + address: currencyAddress, + chainId: currencyChainId, + type: AssetType.Currency, + }, + exactAmountToken: '', + }, + }) } else { setActiveTransactionType(undefined) navigateToSwapFlow({ currencyField, currencyAddress, currencyChainId }) } }, - [currencyAddress, currencyChainId, navigateToSwapFlow, safetyLevel, tokenWarningDismissed], + [ + currencyAddress, + currencyChainId, + navigateToSwapFlow, + safetyLevel, + swappableTokenWithHighestBalance, + tokenWarningDismissed, + ], ) const onPressBuyFiatOnRamp = useCallback((): void => { @@ -367,13 +401,13 @@ function TokenDetails({ {currencyInfo && ( { + closeModalOnly={(): void => { setActiveTransactionType(undefined) setShowWarningModal(false) }} + onAcknowledge={onAcceptWarning} /> )} diff --git a/apps/mobile/src/utils/useOpenBackupReminderModal.ts b/apps/mobile/src/utils/useOpenBackupReminderModal.ts index 30c478bdfce..77b7feceec4 100644 --- a/apps/mobile/src/utils/useOpenBackupReminderModal.ts +++ b/apps/mobile/src/utils/useOpenBackupReminderModal.ts @@ -41,7 +41,7 @@ export function useOpenBackupReminderModal(activeAccount: Account): void { useEffect(() => { if (!enableReminder) { - return + return undefined } if (shouldOpenBackupReminderModal && onboardingBackupExperimentEnabled && backupReminderLastSeenTs === undefined) { @@ -55,6 +55,8 @@ export function useOpenBackupReminderModal(activeAccount: Account): void { return () => clearTimeout(timeoutId) } + + return undefined }, [ dispatch, shouldOpenBackupReminderModal, diff --git a/apps/web/.env b/apps/web/.env index 4d399aa3ef4..43fa8b11cb0 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -4,12 +4,12 @@ REACT_APP_AMPLITUDE_PROXY_URL="https://interface.gateway.uniswap.org/v1/amplitud REACT_APP_AWS_API_ENDPOINT="https://beta.gateway.uniswap.org/v1/graphql" REACT_APP_AWS_REALTIME_ENDPOINT="wss://beta.realtime.gateway.uniswap.org/graphql" REACT_APP_AWS_REALTIME_TOKEN="realtime-3DDE0DA0-3408-4FD0-9DC3-5E285D70D29C" -REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf" +REACT_APP_BNB_RPC_URL="" REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847" -REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://magical-alien-tab.quiknode.pro/669e87e569a8277d3fbd9e202f9df93189f19f4c" -REACT_APP_QUICKNODE_ARBITRUM_RPC_URL="https://black-ultra-valley.arbitrum-mainnet.quiknode.pro/96d7122781cfdcbccf5377cf0c68187332891e79" -REACT_APP_QUICKNODE_ZORA_RPC_URL="https://sly-fabled-breeze.zora-mainnet.quiknode.pro/b9b1f6ade530f3f32c0da16e573fe09eb0eb9132" -REACT_APP_QUICKNODE_ZKSYNC_RPC_URL="https://weathered-young-theorem.zksync-mainnet.quiknode.pro/4cff937b0c3b9c95154711ba0e337052445ef316" +REACT_APP_QUICKNODE_MAINNET_RPC_URL="" +REACT_APP_QUICKNODE_ARBITRUM_RPC_URL="" +REACT_APP_QUICKNODE_ZORA_RPC_URL="" +REACT_APP_QUICKNODE_ZKSYNC_RPC_URL="" REACT_APP_MOONPAY_API="https://api.moonpay.com" REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging" REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz" diff --git a/apps/web/.env.production b/apps/web/.env.production index 736d32a3a80..e280216f3d4 100644 --- a/apps/web/.env.production +++ b/apps/web/.env.production @@ -16,3 +16,4 @@ REACT_APP_STATSIG_PROXY_URL="https://interface.gateway.uniswap.org/v1/statsig-pr REACT_APP_QUICKNODE_MAINNET_RPC_URL="https://ultra-blue-flower.quiknode.pro/770b22d5f362c537bc8fe19b034c45b22958f880" REACT_APP_QUICKNODE_ARBITRUM_RPC_URL="https://tiniest-stylish-arrow.arbitrum-mainnet.quiknode.pro/d06833352b8de605914d9e24a390d8b4d3aff7ba" REACT_APP_IS_UNISWAP_INTERFACE=true +REACT_APP_TRADING_API_KEY=gcZrVL9FxqnqjVytJd2z3oqImkOKRjZ49sF7WXy9 diff --git a/apps/web/cypress/support/e2e.ts b/apps/web/cypress/support/e2e.ts index cd54cee8719..6429dedb4a5 100644 --- a/apps/web/cypress/support/e2e.ts +++ b/apps/web/cypress/support/e2e.ts @@ -19,7 +19,7 @@ registerSetupTests() const log = Cypress.log Cypress.log = function (options, ...args) { if (options.displayName === 'script' || options.name === 'request') { - return + return undefined } return log(options, ...args) } as typeof log diff --git a/apps/web/package.json b/apps/web/package.json index d0afbe03936..6c11ce94c1c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -6,7 +6,7 @@ "scripts": { "ajv": "node scripts/compile-ajv-validators.js", "check:deps:usage": "depcheck", - "check:circular": "concurrently \"../../scripts/check-circular-imports.sh ./src/pages/App.tsx 6\" \"../../scripts/check-circular-imports.sh ./src/setupTests.ts 0\"", + "check:circular": "concurrently \"../../scripts/check-circular-imports.sh ./src/pages/App.tsx 5\" \"../../scripts/check-circular-imports.sh ./src/setupTests.ts 0\"", "sitemap:generate": "node scripts/generate-sitemap.js", "start": "craco start", "start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 REACT_APP_SKIP_CSP=1 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start", @@ -185,9 +185,9 @@ "@types/react-scroll-sync": "0.8.7", "@types/react-window-infinite-loader": "1.0.6", "@uniswap/analytics": "1.7.0", - "@uniswap/analytics-events": "2.36.0", + "@uniswap/analytics-events": "2.37.0", "@uniswap/client-explore": "0.0.9", - "@uniswap/client-pools": "0.0.0", + "@uniswap/client-pools": "0.0.3", "@uniswap/liquidity-staker": "1.0.2", "@uniswap/merkle-distributor": "1.0.1", "@uniswap/permit2-sdk": "1.3.0", @@ -196,7 +196,7 @@ "@uniswap/sdk-core": "5.3.0", "@uniswap/smart-order-router": "3.17.3", "@uniswap/token-lists": "1.0.0-beta.33", - "@uniswap/uniswapx-sdk": "^2.1.0-beta.8", + "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", "@uniswap/universal-router-sdk": "2.2.0", "@uniswap/v2-core": "1.0.1", "@uniswap/v2-periphery": "1.1.0-beta.0", @@ -272,6 +272,7 @@ "styled-components": "5.3.11", "tamagui": "1.108.4", "tiny-invariant": "1.3.1", + "typed-redux-saga": "1.5.0", "ui": "workspace:^", "uniswap": "workspace:^", "use-resize-observer": "9.1.0", diff --git a/apps/web/public/nfts-sitemap.xml b/apps/web/public/nfts-sitemap.xml index 5d053264215..881dd414252 100644 --- a/apps/web/public/nfts-sitemap.xml +++ b/apps/web/public/nfts-sitemap.xml @@ -2,647 +2,697 @@ https://app.uniswap.org/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x60e4d786628fea6478f785a6d7e704777c86a7c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x99a9b7c1116f9ceeb1652de04d5969cce509b069 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb7f7f6c52f2e2fdb1963eab30438024864c313f6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x23581767a106ae21c074b2276d25e5c3e136a68b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xba30e5f9bb24caa003e9f2f0497ad287fdf95623 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xbd3531da5cf5857e7cfaa92426877b022e612cf8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x306b1ea3ecdf94ab739f1910bbda052ed4a9f949 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x1a92f7381b9f03921564a437210bb9396471050c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x5cc5b05a8a13e3fbdb0bb9fccd98d38e50f90c38 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x5af0d9827e0c53e4799bb226655a1de152a425a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x3bf2922f4520a8ba0c2efc3d2a1539678dad5e9d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xe785e82358879f061bc3dcac6f0444462d4b5330 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x76be3b62873462d2142405439777e971754e8e77 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xfd43af6d3fe1b916c026f6ac35b3ede068d1ca01 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x1cb1a5e65610aeff2551a50f76a87a7d3fb649c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x6339e5e072086621540d0362c4e3cea0d643e114 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb932a70a57673d89f4acffbe830e8ed7f75fb9e0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x79fcdef22feed20eddacbb2587640e45491b757f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xa3aee8bce55beea1951ef834b99f3ac60d1abeeb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x769272677fab02575e84945f03eca517acc544cc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x4db1f25d3d98600140dfc18deb7515be5bd293af - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x34eebee6942d8def3c125458d1a86e0a897fd6f9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x59468516a8259058bad1ca5f8f4bff190d30e066 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x394e3d3044fc89fcdd966d3cb35ac0b32b0cda91 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x28472a58a490c5e09a238847f66a68a47cc76f0f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x341a1c534248966c4b6afad165b98daed4b964ef - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xccc441ac31f02cd96c153db6fd5fe0a2f4e6a68d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x764aeebcf425d56800ef2c84f2578689415a2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x160c404b2b49cbc3240055ceaee026df1e8497a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xd2f668a8461d6761115daf8aeb3cdf5f40c532c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x39ee2c7b3cb80254225884ca001f57118c8f21b6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xd774557b647330c91bf44cfeab205095f7e6c367 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x1792a96e5668ad7c167ab804a100ce42395ce54d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x04afa589e2b933f9463c5639f412b183ec062505 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xe75512aa3bec8f00434bbd6ad8b0a3fbff100ad6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x348fc118bcc65a92dc033a951af153d14d945312 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x892848074ddea461a15f337250da3ce55580ca85 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x4b15a9c28034dc83db40cd810001427d3bd7163d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x7ea3cca10668b8346aec0bf1844a49e995527c8b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb852c6b5892256c264cc2c888ea462189154d8d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x9378368ba6b85c1fba5b131b530f5f5bedf21a18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x2acab3dea77832c09420663b0e1cb386031ba17b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x0c2e57efddba8c768147d1fdf9176a0a6ebd5d83 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x08d7c0242953446436f34b4c78fe9da38c73668d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x8943c7bac1914c9a7aba750bf2b6b09fd21037e0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x364c828ee171616a39897688a831c2499ad972ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x7f36182dee28c45de6072a34d29855bae76dbe2f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xf61f24c2d93bf2de187546b14425bf631f28d6dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x797a48c46be32aafcedcfd3d8992493d8a1f256b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x123b30e25973fecd8354dd5f41cc45a3065ef88c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x6632a9d63e142f17a668064d41a21193b49b41a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xf4ee95274741437636e748ddac70818b4ed7d043 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x57a204aa1042f6e66dd7730813f4024114d74f37 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xd1258db6ac08eb0e625b75b371c023da478e94a9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x75e95ba5997eb235f40ecf8347cdb11f18ff640b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xd532b88607b1877fe20c181cba2550e3bbd6b31c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xa1d4657e0e6507d5a94d06da93e94dc7c8c44b51 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xedb61f74b0d09b2558f1eeb79b247c1f363ae452 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x7d8820fa92eb1584636f4f5b8515b5476b75171a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x231d3559aa848bf10366fb9868590f01d34bf240 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xad9fd7cb4fc7a0fbce08d64068f60cbde22ed34c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x0e9d6552b85be180d941f1ca73ae3e318d2d4f1f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb716600ed99b4710152582a124c697a7fe78adbf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x4e1f41613c9084fdb9e34e11fae9412427480e56 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x79986af15539de2db9a5086382daeda917a9cf0c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xc99c679c50033bbc5321eb88752e89a93e9e83c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xc36cf0cfcb5d905b8b513860db0cfe63f6cf9f5c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x3110ef5f612208724ca51f5761a69081809f03b7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x036721e5a769cc48b3189efbb9cce4471e8a48b1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x524cab2ec69124574082676e6f654a18df49a048 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x7ab2352b1d2e185560494d5e577f9d3c238b78c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x32973908faee0bf825a343000fe412ebe56f802a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x7daec605e9e2a1717326eedfd660601e2753a057 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x33fd426905f149f8376e227d0c9d3340aad17af1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x466cfcd0525189b573e794f554b8a751279213ac - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x6be69b2a9b153737887cfcdca7781ed1511c7e36 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x80336ad7a747236ef41f47ed2c7641828a480baa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x9401518f4ebba857baa879d9f76e1cc8b31ed197 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x4b61413d4392c806e6d0ff5ee91e6073c21d6430 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x09233d553058c2f42ba751c87816a8e9fae7ef10 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x960b7a6bcd451c9968473f7bbfd9be826efd549a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x36d30b3b85255473d27dd0f7fd8f35e36a9d6f06 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x698fbaaca64944376e2cdc4cad86eaa91362cf54 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x497a9a79e82e6fc0ff10a16f6f75e6fcd5ae65a8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x41a322b28d0ff354040e2cbc676f0320d8c8850d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xa9c0a07a7cb84ad1f2ffab06de3e55aab7d523e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x942bc2d3e7a589fe5bd4a5c6ef9727dfd82f5c8a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x8821bee2ba0df28761afff119d66390d594cd280 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x8c6def540b83471664edc6d5cf75883986932674 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x8d9710f0e193d3f95c0723eaaf1a81030dc9116d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x86825dfca7a6224cfbd2da48e85df2fc3aa7c4b1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x629a673a8242c2ac4b7b8c5d8735fbeac21a6205 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x9a534628b4062e123ce7ee2222ec20b86e16ca8f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xc2c747e0f7004f9e8817db2ca4997657a7746928 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x73da73ef3a6982109c4d5bdb0db9dd3e3783f313 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xc92ceddfb8dd984a89fb494c376f9a48b999aafc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x3248e8ba90facc4fdd3814518c14f8cc4d980e4b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x67d9417c9c3c250f61a83c7e8658dac487b56b09 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb6a37b5d14d502c3ab0ae6f3a0e058bc9517786e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x86c10d10eca1fca9daf87a279abccabe0063f247 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x4b3406a41399c7fd2ba65cbc93697ad9e7ea61e5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xb0640e8b5f24bedc63c33d371923d68fde020303 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xd3d9ddd0cf0a5f0bfb8f7fceae075df687eaebab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xa5c0bd78d1667c13bfb403e2a3336871396713c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x4d7d2e237d64d1484660b55c0a4cc092fa5e6716 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xfcb1315c4273954f74cb16d5b663dbf479eec62e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x66d1db16101502ed0ca428842c619ca7b62c8fef - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x128675d4fddbc4a0d3f8aa777d8ee0fb8b427c2f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x19b86299c21505cdf59ce63740b240a9c822b5e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xacf63e56fd08970b43401492a02f6f38b6635c91 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0x0bebad1ff25c623dff9605dad4a8f782d5da37df - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.7 https://app.uniswap.org/nfts/collection/0xdceaf1652a131f32a821468dc03a92df0edd86ea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x273f7f8e6489682df756151f5525576e322d51a3 + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x77372a4cc66063575b05b44481f059be356964a4 + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf5b0a3efb8e8e4c201e2a935f110eaaf3ffecb8d + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x22c36bfdcef207f9c0cc941936eff94d4246d14a + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x59325733eb952a92e069c87f0a6168b29e80627f + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x0e3a2a1f2146d86a604adc220b4967a898d7fe07 + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x3af2a97414d1101e2107a70e7f33955da1346305 + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5ab21ec0bfa0b29545230395e3adaca7d552c948 + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x617913dd43dbdf4236b85ec7bdf9adfd7e35b340 + 2024-09-20T21:06:11.923Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x3fe1a4c1481c8351e91b64d5c398b159de07cbc5 + 2024-09-20T21:06:11.923Z 0.7 \ No newline at end of file diff --git a/apps/web/public/pools-sitemap.xml b/apps/web/public/pools-sitemap.xml index 0903b0778ae..32c6ec25268 100644 --- a/apps/web/public/pools-sitemap.xml +++ b/apps/web/public/pools-sitemap.xml @@ -2,4357 +2,6507 @@ https://app.uniswap.org/explore/pools/ethereum/0xcbcdf9626bc03e24f779434178a73a0b4bad62ed - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4e68ccd3e89f51c3074ca5072bbac773960dfa36 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4585fe77225b41b697c938b018e2ac67ac5a20c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc63b0708e2f7e69cb8a1df0e1389a98c35a76d52 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x99ac8ca7087fa4a2a1fb6357269965a2014abc35 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x11b815efb8f581194ae79006d24e0d814b7697f6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa6cc3c2531fdaa6ae1a3ca84c2855806728693e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x5777d92f208679db4b9778590fa3cab3ac9e2168 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x1d42064fc4beb5f8aaf85f4617ae8b3b5b8bd801 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc2e9f25be6257c210d7adf0d4cd6e3e881ba25f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x11950d141ecb863f01007add7d1a342041227b58 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc5c134a1f112efa96003f8559dba6fac0ba77692 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x109830a1aaad605bbf02a9dfa7b0b92ec2fb7daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x1df4c6e36d61416813b42fe32724ef11e363eddc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x12d6867fa648d269835cf69b49f125147754b54d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x3416cf6c708da44db2624d63ea0aaef7113527c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xe8c6c9227491c0a8156a0106a0204d881bb7e531 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x04708077eca6bb527a5bbbd6358ffb043a9c1c14 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x9db9e0e53058c89e5b94e29621a205198648425b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xf239009a101b6b930a527deaab6961b6e7dec8a6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xfe0df74636bc25c7f2400f22fe7dae32d39443d2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xf4c5e0f4590b6679b3030d29a84857f226087fef - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x5764a6f2212d502bc5970f9f129ffcd61e5d7563 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa3f558aebaecaf0e11ca4b2199cc5ed341edfd74 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x99132b53ab44694eeb372e87bced3929e4ab8456 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x6c6bc977e13df9b0de53b251522280bb72383700 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x9d96880952b4c80a55099b9c258250f2cc5813ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x3afdc5e6dfc0b0a507a8e023c9dce2cafc310316 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x290a6a7460b308ee3f19023d2d00de604bcf5b42 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xac4b3dacb91461209ae9d41ec517c2b9cb1b7daf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x60594a405d53811d3bc4766596efd80fd545a270 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x331399c614ca67dee86733e5a2fba40dbb16827c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x844eb5c280f38c7462316aad3f338ef9bda62668 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xe936f0073549ad8b1fa53583600d629ba9375161 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x2f62f2b4c5fcd7570a709dec05d68ea19c82a9ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x381fe4eb128db1621647ca00965da3f9e09f4fac - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x97e7d56a0408570ba1a7852de36350f7713906ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xcd423f3ab39a11ff1d9208b7d37df56e902c932b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xe15e6583425700993bd08f51bf6e7b73cd5da91b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x69d91b94f0aaf8e8a2586909fa77a5c2c89818d5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xe42318ea3b998e8355a3da364eb9d48ec725eb45 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xad9ef19e289dcbc9ab27b83d2df53cdeff60f02d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x3b685307c8611afb2a9e83ebc8743dc20480716e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x7bea39867e4169dbe237d55c8242a8f2fcdcc387 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x7b1e5d984a43ee732de195628d20d05cfabc3cc7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x7858e59e0c01ea06df3af3d20ac7b0003275d4bf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xae2a25cbdb19d0dc0dddd1d2f6b08a6e48c4a9a9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x14af1804dbbf7d621ecc2901eef292a24a0260ea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x80a9ae39310abf666a87c743d6ebbd0e8c42158e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc31e54c7a869b9fcbecc14363cf510d1c41fa443 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2f5e87c9312fa29aed5c179e456625d79015299c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc6962004f452be9203591991d15f6b388e09e8d0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc6f780497a95e246eb9449f5e4770916dcd6396a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x641c00a822e8b671738d32a431a4fb6074e5c79d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x92c63d0e701caae670c9415d91c474f686298f00 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x1aeedd3727a6431b8f070c0afaa81cc74f273882 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xcda53b1f66614552f834ceef361a8d12a0b8dad8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x35218a1cbac5bbc3e57fd9bd38219d37571b3537 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x17c14d2c404d167802b16c450d3c99f88f2c4f4d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x468b88941e7cc0b88c1869d68ab6b570bcef62ff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xdbaeb7f0dfe3a0aafd798ccecb5b22e708f7852c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x149e36e72726e0bcea5c59d40df2c43f60f5a22d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xbaaf1fc002e31cb12b99e4119e5e350911ec575b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa67f72f21bd9f91db2da2d260590da5e6c437009 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x92fd143a8fa0c84e016c2765648b9733b0aa519e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x7cf803e8d82a50504180f417b8bc7a493c0a0503 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x81c48d31365e6b526f6bbadc5c9aafd822134863 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x446bf9748b4ea044dd759d9b9311c70491df8f29 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc82819f72a9e77e2c0c3a69b3196478f44303cf4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x50c7390dfdd3756139e6efb5a461c2eb7331ceb4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x1dfc1054e0e2a10e33c9ca21aad5aa8a1cce91e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc91b7b39bbb2c733f0e7459348fd0c80259c8471 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x59d72ddb29da32847a4665d08ffc8464a7185fae - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x09ba302a3f5ad2bf8853266e271b005a5b3716fe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa77d77c9773c35e910acc2e30cefe52b54a58414 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x8da66e470403b3d3eee66c67e2c61fda6e248ad1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2f020e708811c054f146eebcc4d5a215fd4eec26 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x7e7fb3cceca5f2ac952edf221fd2a9f62e411980 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x68c685fd52a56f04665b491d491355a624540e85 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa8328bf492ba1b77ad6381b3f7567d942b000baf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc0cf0f380ddb44dbcaf19a86d094c8bba3efa04a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa169d1ab5c948555954d38700a6cdaa7a4e0c3a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x1862200e8e7ce1c0827b792d0f9546156f44f892 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x05bbaaa020ff6bea107a9a1e06d2feb7bfd79ed2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xd02a4969dc12bb889754361f8bcf3385ac1b2077 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc24f7d8e51a64dc1238880bd00bb961d54cbeb29 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x7c06736e41236fecd681dd3353aa77ecd19ea565 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc473e2aee3441bf9240be85eb122abb059a3b57c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x14353445c8329df76e6f15e9ead18fa2d45a8bb6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2039f8c9cd32ba9cd2ea7e575d5b1abea93f7527 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xd3e11119d2680c963f1cdcffece0c4ade823fb58 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x8e295789c9465487074a65b1ae9ce0351172393f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x97bca422ec0ee4851f2110ea743c1cd0a14835a1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xbe3ad6a5669dc0b8b12febc03608860c31e2eef6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x56ebd63a756b94d3de9cea194896b4920b64fb01 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe2ddd33585b441b9245085588169f35108f85a6e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x84436a2af97f37018db116ae8e1b691666db3d00 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x68f5c0a2de713a54991e01858fd27a3832401849 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x4533bad2dc588f0fadf8d2e72386d4cd6a19b519 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x85149247691df622eaf1a8bd0cafd40bc45154a9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x0392b358ce4547601befa962680bede836606ae2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x1c3140ab59d6caf9fa7459c6f83d4b52ba881d36 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd1f1bad4c9e6c44dec1e9bf3b94902205c5cd6c3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x03af20bdaaffb4cc0a521796a223f7d85e2aac31 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x73b14a78a0d396c521f954532d43fd5ffe385216 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xac85eaf55e9c60ed40a683de7e549d23fdfbeb33 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x04f6c85a1b00f6d9b75f91fd23835974cc07e65c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x730691cdac3cbd4d41fc5eb9d8abbb0cea795b94 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x535541f1aa08416e69dc4d610131099fa2ae7222 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xfc1f3296458f9b2a27a0b91dd7681c4020e09d05 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x85c31ffa3706d1cce9d525a00f1c7d4a2911754c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd52533a3309b393afebe3176620e8ccfb6159f8a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xff7fbdf7832ae524deda39ca402e03d92adff7a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xb589969d38ce76d3d7aa319de7133bc9755fd840 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xf334f6104a179207ddacfb41fa3567feea8595c2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x1fb3cf6e48f1e7b10213e7b6d87d4c073c7fdb7b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd4344ea0c5ade7e22b9b275f0bde7a145dec5a23 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x5b42a63d6741416ce9a7b9f4f16d8c9231ccddd4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x252cbdff917169775be2b552ec9f6781af95e7f6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x2ab22ac86b25bd448a4d9dc041bd2384655299c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xc858a329bf053be78d6239c4a4343b8fbd21472b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa73c628eaf6e283e26a7b1f8001cf186aa4c0e8e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xb533c12fb4e7b53b5524eab9b47d93ff6c7a456f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x2ae3d6096d8215ac2acddf30c60caa984ea5debe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x19ea026886cbb7a900ecb2458636d72b5cae223b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x6f32061f59a21086c334d0d45f804089ce374aaf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xfaf037caafa9620bfaebc04c298bf4a104963613 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xadb35413ec50e0afe41039eac8b930d313e94fa4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xe9e3893921de87b1194a8108f9d70c24bde71c27 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xf1f199342687a7d78bcc16fce79fa2665ef870e1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xf44acaa38be5e965c5ddf374e7a2ba270e580684 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x36e42931a765022790b797963e42c5522d6b585a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x5adba6c5589c50791dd65131df29677595c7efa7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x3249e3e3e4133ee18e65347daf586610cc265f54 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xca1b837c87c6563910c2befa48834fa2a8c3d72d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x6ef7b14bcd8d989cef8f8ec8ba4bf371b2ac95fd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x37ffd11972128fd624337ebceb167c8c0a5115ff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xe62bd99a9501ca33d98913105fc2bec5bae6e5dd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xb2ac2e5a3684411254d58b1c5a542212b782114d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xb0efaf46a1de55c54f333f93b1f0641e73bc16d0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd0fa3b5264ccde31e8b094b86bca4a1e97d3c603 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xad4c666fc170b468b19988959eb931a3676f0e9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x790fde1fd6d2568050061a88c375d5c2e06b140b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xaefc1edaede6adadcdf3bb344577d45a80b19582 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa8a5356ee5d02fe33d72355e4f698782f8f199e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x55bc964fe3b0c8cc2d4c63d65f1be7aef9bb1a3c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x95d9d28606ee55de7667f0f176ebfc3215cfd9c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x45dda9cb7c25131df268515131f647d726f50608 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x50eaedb835021e4a108b7290636d62e9765cc6d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x167384319b41f7094e62f7506409eb38079abff8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa374094527e1673a86de625aa59517c5de346d32 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x86f1d8390222a3691c28938ec7404a1661e618e0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xeda1094f59a4781456734e5d258b95e6be20b983 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x847b64f9d3a95e977d157866447a5c0a5dfa0ee5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x94ab9e4553ffb839431e37cc79ba8905f45bfbea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x0e44ceb592acfc5d3f09d996302eb4c499ff8c10 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x1e5bd2ab4c308396c06c182e1b7e7ba8b2935b83 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x9b08288c3be4f62bbf8d1c20ac9c5e6f9467d8b7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xb6e57ed85c4c9dbfef2a68711e9d6f36c56e0fcb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x3e31ab7f37c048fc6574189135d108df80f0ea26 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xd36ec33c8bed5a9f7b6630855f1533455b98a418 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xdac8a8e6dbf8c690ec6815e0ff03491b2770255d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xfe343675878100b344802a6763fd373fdeed07a4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x0a28c2f5e0e8463e047c203f00f649812ae67e4f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x88f3c15523544835ff6c738ddb30995339ad57d6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x98b9162161164de1ed182a0dfa08f5fbf0f733ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xeef1a9507b3d505f0062f2be9453981255b503c8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc4c06c9a239f94fc0a1d3e04d23c159ebe8316f1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x849ec65748107aedc518dbc42961f358ea1361a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2db87c4831b2fec2e35591221455834193b50d1b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa4d8c89f0c20efbe54cba9e7e7a7e509056228d9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x642f28a89fa9d0fa30e664f71804bfdd7341d21f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2aceda63b5e958c45bd27d916ba701bc1dc08f7a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x781067ef296e5c4a4203f81c593274824b7c185d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x4ccd010148379ea531d6c587cfdd60180196f9b1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xd866fac7db79994d08c0ca2221fee08935595b4b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x941061770214613ba0ca3db9a700c39587bb89b6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa9077cdb3d13f45b8b9d87c43e11bce0e73d8631 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa01f64fa1b923dd9c5c7618b39a6ba8098a88863 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa830ff28bb7a46570a7e43dc24a35a663b9cfc2e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x8837a61644d523cbe5216dde226f8f85e3aa9be3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xca5d44977d6de1846530eb434167b208752fba7d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x4d05f2a005e6f36633778416764e82d1d12e7fbb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x41e64a5bc929fa8e6a9c8d7e3b81a13b21ff3045 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x3ea34cfc9322273311f7843826a2581c4a00fd39 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x785061ed819414dc4269d2a5d5974069c0daea96 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x3f5228d0e7d75467366be7de2c31d0d098ba2c23 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2e3f22e9a1c2470b2e293351f48c99e1fd788f32 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2a08c38c7e1fa969325e2b64047abb085dec3756 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xe6c36eed27c2e8ecb9a233bf12da06c9730b5955 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xefa98fdf168f372e5e9e9b910fcdfd65856f3986 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x76fa081e510f43ac8335efdb4db88c9ff1894413 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc6832ef0af793336aa44a936e54b992bff47e7cd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x865f456479a21e2b3d866561d7171a3d0a7b112d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xbd934a7778771a7e2d9bf80596002a214d8c9304 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x9ab9f658104467604b5afa9a3e1df62f35f7b208 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x6e430d59ba145c59b73a6db674fe3d53c1f31cae - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x9e37cb775a047ae99fc5a24dded834127c4180cd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x48413707b70355597404018e7c603b261fcadf3f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xade9bcd4b968ee26bed102dd43a55f6a8c2416df - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xda679706ff21114ac9fac5198bff24543f357a16 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xba3f945812a83471d709bce9c3ca699a19fb46f7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc9034c3e7f58003e6ae0c8438e7c8f4598d5acaa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x4c36388be6f416a29c8d8eee81c771ce6be14b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa1b2457c0b627f97f6cc892946a382451e979014 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x4b0aaf3ebb163dd45f663b38b6d93f6093ebc2d3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xae2ce200bdb67c472030b31f602f0756c9aeb61c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x3bc5180d5439b500f381f9a46f15dd6608101671 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x5122e02898ece3bc62df8c1efdb29a9e914244d3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x24e1cbd6fed006ceed9af0dce688acc7951d57a9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2556230ac694093d4d3b7b965a2f2d77d4c403a4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xdaca082c2c7d052a96fa83ea9d3a7b6839e39586 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa555149210075702a734968f338d5e1cbd509354 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x10648ba41b8565907cfa1496765fa4d95390aa0d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x00bcec1526dae1e170a53017b8775a93b7810d7c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x20e068d76f9e90b90604500b84c7e19dcb923e7e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x6b93950a9b589bc32b82a5df4e5148f98a7fae27 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd9caa6dbe6791fcb7fc9fb59d1a6b3dd8c1c2339 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x62e81e93136ac42a1ada48d4098f5f9e703e7455 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x84206d33845c9d811438b6fe4e7a0c634748dc50 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd0b53d9277642d899df5c87a3966a349a798f224 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xcfa7c4bb565915f1c4f9475e2a0536d31efad776 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa7de21f28ca460b45373b217cd4eb111c3faeff8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xb64dff20dd5c47e6dbb56ead80d23568006dec1e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xad4e969f4193878e5cc89cefb57faf6c7c0048da - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xdf5eb97e3e23ca7f5a5fd2264680377c211310ba - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xf16baaae8eb7b37f4280e72924479f69e7a61f32 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xe745a591970e0fa981204cf525e170a2b9e4fb93 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x64b74c66b9ba60ca668b781289767ae7298f37ae - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x17e1ebd791e7253a5e606fd94c5b66c14d873136 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x46715bd57b9ec01deadb35fe096fb44acda79414 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x3447accd4b8e735329d1065244aad2ed630f0122 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2feb7f3ffc243f7de94d5ea5975533d301584e07 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0d5959a52e7004b601f0be70618d01ac3cdce976 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2170ca774e48a3f51559917ada6f9d7ae8f7bfea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x62a76dfa8951aefcff787e790782db3633ebf422 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x8073679e0b3b2d1d665777cf1b2b5b1c2d3d2d0c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x143f1a6f3fb32e6ab3f22d3cc6b417b5c2197599 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x82ad659c2f152aad59bb37cbc5e7663a2de0c607 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa4efe9e8e2a2d5a2ac46805f233b8e49d0e11955 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xfcc89a1f250d76de198767d33e1ca9138a7fb54b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2faa2b42b782d578a160f61bb7cd763a17476730 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xdd44c0e83c2570062d1e6fdd440b4724862e8f31 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xe3930a14641786e123e7bbe842d701fa1cbfe2df - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x6d03360ce4764e862ed81660c1f76cc2711b14b6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc055f66f228105072315247785c00299d0ce27e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xcae1d141ab11cef0a415cf0440025e1e5e962e06 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0f338ec12d3f7c3d77a4b9fcc1f95f3fb6ad0ea6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x4eaa90264d6a3567228dcb5cfc242200da586437 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x6fe9e9de56356f7edbfcbb29fab7cd69471a4869 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xf420603317a0996a3fce1b1a80993eaef6f7ae1a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x47a90a2d92a8367a91efa1906bfc8c1e05bf10c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x41bf5eeae051fbd2e97b76b5f8f0fdcc1a1e526b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x28df0835942396b7a1b7ae1cd068728e6ddbbafd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa3f3664a52f01b42557524bd14556e379daf5669 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x1fd22fa7274bafebdfb1881321709f1219744829 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xe39cfc1a2e51a09ecbd060a24ee4eef5a97697bb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x06396509195eb9e07c38a016694dc9ff535b128a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x5a1c486edefda2f09d3b349fadc38524f1743826 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x5bf1cf153c102a79d9e18b7fb7c79ba57fa70d0c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2c3c320d49019d4f9a92352e947c7e5acfe47d68 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x4141325bac36affe9db165e854982230a14e6d48 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x17507bef4c3abc1bc715be723ee1baf571256e05 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x8149b92ea743cc382aada523b68b8834733b9015 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xc98f01bf2141e1140ef8f8cad99d4b021d10718f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x7f9d307973cdabe42769d9712df8ee1cc1a28d10 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x5c87da28a45e5089b762dcbbd86f743d14c54317 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2cd97604ef77bbcb1fa0cff47545dff8ec7def08 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x7862d9b4be2156b15d54f41ee4ede2d5b0b455e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x554548b404213c7efcdbab933f52edfe3c581834 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x63008c5ea4e47f5421e0e1428b1c5043a507d0d0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0350ca994791c4b07a5b02b08aaf9d6fc8ab510e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x32776ed4d96ed069a2d812773f0ad8ad9ef83cf8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x84f3ca9b7a1579ff74059bd0e8929424d3fa330e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x5289a8dbf7029ee0b0498a84777ed3941d9acfec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xb2bc284ab4c953b7f7a06d59c0ceb2de26405f22 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x508acf810857fefa86281499068ad5d19ebce325 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xccdfcd1aac447d5b29980f64b831c532a6a33726 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x4fb87838a29b37598099ef5aa6b3fbeeef987c50 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x515e94dc736b9d8b7d28ecf1cece0aba3d75da97 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xfd6e5b7c30538dff2752058e425ad01a56b831cc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xcb99fe720124129520f7a09ca3cbef78d58ed934 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xd2f21358c1549be193537b2a4c5dc7f0228ae011 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x93094ed1c907e4bca7eb041cb659da94f7e1b58e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xd37e6ecb991d1a0e7610c89666817665713362a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x73234630bd159384c8d43f145407312d64614f43 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xad1ddf00c4ae50573e4dc98e6c5ee93baa04a0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa765593c821f7df9ad81119509a37961e7ffa6c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x9b501a7ad3087d603ceb34424b7b2a6c348ad0b7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xafebb7cfa1a15fcac4121b609b456cbce3137c20 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0adaf134ae0c4583b3a38fc3168a83e33162651e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xf9878a5dd55edc120fde01893ea713a4f032229c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x84e47c7f2fe86f6b5efbe14fee46b8bb871b2e05 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xf3e5bec78654049990965f666b0612e116b94fb2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x33e59edd3214e97cb68450c6d3d6c167de072aba - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2ca76c7e466e560e0cb11a91269bb953e41254bc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xbb124e35ab9e85f8d59ba83500e559dc052b9368 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xd88d5f9e6c10e6febc9296a454f6c2589b1e8fae - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xb90fe7da36ac89448e6dfd7f2bb1e90a66659977 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xbd6313d0796984c578cae6bc5b5e23b27c5540c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x1f18cd7d1c7ba0dbe3d9abe0d3ec84ce1ad10066 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x7da99753ff017f1b7afb2c8c0542718dc9f15f21 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x079e7a44f42e9cd2442c3b9536244be634e8f888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x1c8dafd358d308b880f71edb5170b010b106ca60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xbd0f6f34baa3c1329448a69bab90111a20756f01 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x3420720e561f3082f1e514a4545f0f2e0c955a5d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xea3fb6e3313a2a90757e4ca3d6749efd0107b0b6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xf130f72f8190f662522774c3367e6e8814f5e219 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x4a46c053bd5c10a959aea258228217b9d3405f3d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xb83258bf5940c98abf54f26c5a02710bd6b83b2c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x6a209c5329f0a225fa1890d4177823c096016f34 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xdb24905b1b080f65dedb0ad978aad5c76363d3c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xddff2cdad11898b901a661e32e9fa010780263a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x72dd8fe09b5b493012e5816068dfc6fb26a2a9e6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x54fc722a66abfb6500a36d8b7b2646129d0e836a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x53b612b32233c80ec439a64325a29766ce95be7f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xe5edcbe72d1bc223097a1bed1fe6c0e404b4290c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xb928c37b8bd9754d321dc3d3c6ef374d332fe761 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x2d70cbabf4d8e61d5317b62cbe912935fd94e0fe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x953e2937f0515c43ca7995e80c84aedcbbb9385e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x84394d80830ae963b599ded7d9149b90059f182f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa1777e082fa1746eb78dd9c1fbb515419cf6e538 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x112466c8b6e5abe42c78c47eb1b9d40baa3f943c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x9491d57c5687ab75726423b55ac2d87d1cda2c3f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x978799f1845c00c9a4d9fd2629b9ce18df66e488 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xdc55d1fd1c04e005051a40bd59c5f95623257bc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x34757893070b0fc5de37aaf2844255ff90f7f1e0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x7faf167615419228f3f7d71d52d840dab154913c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa4d7b6a50dd4c55334ca6f175dbc6561f269d264 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x0ed413cefde954d8e5c54d981d7d182b587e98e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x524375d0c6a04439128428f400b00eae81a2e9e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x4b7a4530d56ff55a4dce089d917ede812e543307 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x84bb5b9bf1b6782c87cfa3e396f2f571c8e49646 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x723292eea7e1576ae482a5c317934054c0199e24 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x9b42940e8184d866aac6595a91f8d8952a59d3b9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x37622453c614f625d288151101ffe48fd222ced1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x4a94130b9e8eb0a0959c2c0f1ee9583213773fd9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x51514b3dc24afc1db95586242b99f0063bea17c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xc130254e9196d48bbd9f91240390a6e8203132e9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x60ac25da2ada3be14a2a8c04e45b072bed965966 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x4e392a3883a84225260ff857318517eb50e5d128 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xca0aa06385a42242fe9523cd7015f6d01cd8f6b2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x3e448c17043ce1481bbe53c0fd19481bad8b98a6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x81060e6bf2a683f208b8799a33c7c09830cabed1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x463fe9f646b61ccfb43a022bf947075411cd71c7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x21b8065d10f73ee2e260e5b47d3344d3ced7596e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x517f9dd285e75b599234f7221227339478d0fcc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa43fe16908251ee70ef74718545e4fe6c5ccec9f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x0af81cd5d9c124b4859d65697a4cd10ee223746a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xca7c2771d248dcbe09eabe0ce57a62e18da178c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x09d1d767edf8fa23a64c51fa559e0688e526812f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x7b73644935b8e68019ac6356c40661e1bc315860 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x180efc1349a69390ade25667487a826164c9c6e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x9c4fe5ffd9a9fc5678cfbd93aa2d4fd684b67c4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa478c2975ab1ea89e8196811f51a7b7ade33eb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xbb2b8038a1640196fbe3e38816f3e67cba72d940 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x9ec9367b8c4dd45ec8e7b800b1f719251053ad60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xc91ef786fbf6d62858262c82c63de45085dea659 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x197d7010147df7b99e9025c724f13723b29313f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x25647e01bd0967c1b9599fa3521939871d1d0888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x2f0b1417aa42ebf0b4ca1154212847f6094d708d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x6ada49aeccf6e556bb7a35ef0119cc8ca795294a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x2a6c340bcbb0a79d3deecd3bc5cbc2605ea9259f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xda2d09fbbf8ee4b5051a0e9b562c5fcb4b393b18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x48d20b3e529fb3dd7d91293f80638df582ab2daa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x4028daac072e492d34a3afdbef0ba7e35d8b55c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xc2eab7d33d3cb97692ecb231a5d0e4a649cb539d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xc5be99a02c6857f9eac67bbce58df5572498f40c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xe4b8583ccb95b25737c016ac88e539d0605949e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x8dbee21e8586ee356130074aaa789c33159921ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x43de4318b6eb91a7cf37975dbb574396a7b5b5c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x9ff68f61ca5eb0c6606dc517a9d44001e564bb66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa29fe6ef9592b5d408cca961d0fb9b1faf497d6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x1b1137dd16faa651e38a9dfb5d9ffff7767fdf62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x470e8de2ebaef52014a47cb5e6af86884947f08c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x8fb8e9921922d2ffb529a95d28a0d06d275d7a59 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xd3d2e2692501a5c9ca623199d38826e513033a17 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x97e1fcb93ae7267dbafad23f7b9afaa08264cfd8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa5e9c917b4b821e4e0a5bbefce078ab6540d6b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x2cc846fff0b08fb3bffad71f53a60b4b6e6d6482 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x959873fb4fc11825fba83c80c4c632db1e936e15 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa7480aafa8ad2af3ce24ac6853f960ae6ac7f0c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xc7e6b676bfc73ae40bcc4577f22aab1682c691c6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x570febdf89c07f256c75686caca215289bb11cfc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x343fd171caf4f0287ae6b87d75a8964dc44516ab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xcaa004418eb42cdf00cb057b7c9e28f0ffd840a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xe3d3551bb608e7665472180a20280630d9e938aa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xb6b0c651c37ec4ca81c0a128420e02001a57fac2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x4e34da137f0b317c633838458e0c923a5e088752 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xfe9e7931e55c514c33d489c88582fa36e84bd8e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x5281e311734869c64ca60ef047fd87759397efe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x149148acc3b06b8cc73af3a10e84189243a35925 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x8ef79d6c328c25da633559c20c75f638a4863462 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xbf16ef186e715668aa29cef57e2fd7f9d48adfe6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x5645dcb64c059aa11212707fbf4e7f984440a8cf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x3ad4913fa896391c9822a81d8d869cc0d783bdd7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x0f23d49bc92ec52ff591d091b3e16c937034496e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x7a415b19932c0105c82fdb6b720bb01b0cc2cae3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x9b3423373e6e786c9ac367120533abe4ee398373 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4a25dbdf9629b1782c3e2c7de3bdce41f1c7f801 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xbe80225f09645f172b079394312220637c440a63 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x059615ebf32c946aaab3d44491f78e4f8e97e1d3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x435664008f38b0650fbc1c9fc971d0a3bc2f1e47 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x4b62fa30fea125e43780dc425c2be5acb4ba743b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xc3db44adc1fcdfd5671f555236eae49f4a8eea18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/ethereum/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe5cf22ee4988d54141b77050967e1052bd9c7f7a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x7f580f8a02b759c350e6b8340e7c2d4b8162b6a9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x48b0ab72c2591849e678e7d6f272b75ef9b863f7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x74d0ae8b8e1fca6039707564704a25ad2ee036b0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x5969efdde3cf5c0d9a88ae51e47d721096a97203 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe32efff8f8b5fdc53803405aa3f623f03f8a8767 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe8629b6a488f366d27dad801d1b5b445199e2ada - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x066b28f0c160935cf285f75ed600967bf8417035 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x146b020399769339509c98b7b353d19130c150ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd28f71e383e93c570d3edfe82ebbceb35ec6c412 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xadab76dd2dca7ae080a796f0ce86170e482afb4a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x0fb07e6d6e1f52c839608e1436d2ea810cf07257 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x95d2483d2a0fff034004f91c53d649623d993896 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x19c5505638383337d2972ce68b493ad78e315147 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc143161ed3ed8049bb63d8da42907c08a10e2269 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xc3286373599dd5af2a17a572ebb7561f05f88bec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xbb98b3d2b18aef63a3178023a920971cf5f29be4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x647fb01a63de9a551b39c7915693b25e6bcec502 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa90c1c009dc8292bd04ced30f9b53a5ff7a806a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/polygon/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xfb765ff72a14735550f1d798a5efd1311f2ddee7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x3537f2a5f99f08f59eb1417073db1fadbebf0c74 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xde8ed0277ee0e84c25756a73ffa7374e4aeadf46 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd8f3a72d2b2220a5067abe8c38aea57dc2d69a5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x7ec18abf80e865c6799069df91073335935c4185 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x14b1911dd6b451c2771661ae8cd70637d726c356 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x9ae8084c21752971d867597c07f2673765d949a1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xcfaf75a3d292c3535ea3acdb16ed2ee58c2bb091 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x8055e6de251e414e8393b20adab096afb3cf8399 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xffec10fe1355c2d8df4f62affcdeffdb04f06569 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc16454420f100b2e771d8bc4c5b6200068129a34 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x046f405e4ae1d0e786eda4959adadbd417d13ad8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xeccb34691c06c1c9c31ceb2228b22cbd242b5879 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xe22a2dfaaaaec8a7b2b7acb4909eaaa5c5bd6e64 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xe2dda0911e227e73d9fd94745b851c8bc6504610 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0f082a7870908f8cebbb2cd27a42a9225c19f898 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x69d667281778db0c3bc8177efea3a91ee95c3068 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x30d61bb28a6789f9f49d8c7fb198d63b6aba4b61 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x090f3fd9110621df127c3f9be5c6f58c02f2d5eb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd56f086e7b796b313d49f2bc926fac4bdd2a2b0b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x7eb847a214192aab8fa1b503f4d4c9ddd2a08db6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x81b3bc0ef974c16d71b8614adb8c22ccc045da01 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xc9b44ca4159dbaf5722a3dc8618e9d4b5f39d5b2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xbeef35a63fc62a3334630d9d3b4db27093d95317 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x3d5d143381916280ff91407febeb52f2b60f33cf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x68c9325cc268df8b9ed4a06429587f28471b5f84 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa00cc1fb7ac185222294777c6b23a13c013f07ce - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x77021e63bcbd3c5296b0cdd8a3c3770fb0ea8fa2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xcc28456d4ff980cee3457ca809a257e52cd9cdb0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xec0b7e8e44c9d60efd67a89dba1d4a6e02a7a4a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0c8fed5dd65542ca5f0add1acab14c2e470c9110 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd56da2b74ba826f19015e6b7dd9dae1903e85da1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x5482c2b11951bbb92b87858242e17abde802b398 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xd95bae63641d822dc591bd4aca7a64e53eac76f9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x06959273e9a65433de71f5a452d529544e07ddd0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x24bf2ee2e09477082d1ddf2f0603baa460b3f5f3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x56d8f846415e08c5e663d89505e79f522d33f947 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x548e923281f372d28a40287d3a2d30dce482fc66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x9d744d3d905897608d24c1b8c1c7db0d30c36cd4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/base/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xab46d39cb398fb3649ecba781180016fef75f50b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x25048028ad87484b7fce99bc4e22dcb6c3307470 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xdb2177fee5b0ebdc7b8038cb70f3964bb6d14143 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x42d749f736051d8933b118324cded52d1f92bec1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xb1a1b707b143b911c36e1a0f4f901c5017791aca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x3319a81a316abd4c086f7048904e31ff86648b38 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x4a978a2d4fb7393063babfb0cee741b8bcd4dd4b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xea403e36fb592fdfdc342c38e94284ddbb0d2105 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xe3fb01794d6912f0773171e32e723471ee8df061 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x916d7f23ccbb1d10118dcfc6ad5a10b6446ff73e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/bnb/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x6cde5f5a192fbf3fd84df983aa6dc30dbd9f8fac - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xd80d28850bebe6208433c298334392bc940b4fc7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x7f7c4335ccac291ddedcef4429a626c442b627ed - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x628cb3a5a206956423d158009612813b64b19dab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x116361f4f45e310347b43cd098fdfa459760ea7f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x5dc631ad6c26bea1a59fbf2c2680cf3df43d249f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x1a810e0b6c2dd5629afa2f0c898b9512c6f78846 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xac1cb6d3d419da9ead0b53e62d6fb4bb53473523 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x0115d04a88990889471a88e85817aac9e961c07b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xd3409b7f3f54bb097433d0f4cd31c48ac33e569b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x493bfc1adb2e60805693197f23132350ffd2a04e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xcf4f103759770c21f945413781ca787620316988 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xb135ebde27d366b0d62e579bae4118cb991b820e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xecbc2f008c20729b9239317408367377c5473812 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x96e0c440d3377c2dfe4f2a82add0b045e46cbe64 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x6f5304c22ac77e228e8af4732ac6677c46e09030 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xcb037f27eb3952222810966e28e0ceb650c65cd9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xddd23787a6b80a794d952f5fb036d0b31a8e6aff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xa86aca6d7c393c06dcdc30473ea3d1b05c358dff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x1ffec7119e315b15852557f654ae0052f76e6ae1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x0f027d40c80d8f70f77d3884776531f80b21d20e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x69c66beafb06674db41b22cfc50c34a93b8d82a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xeedff72a683058f8ff531e8c98575f920430fdc5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x811cfb75567a252bea23474e2ccd1286927bfe0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x2caccf71bdf8fff97c06a46eca29b611b1a74b5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0xf07a84f0732dfe8eea0d3961bcd8f62c761ff508 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/pools/celo/0x8c1c499b1796d7f3c2521ac37186b52de024e58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x7baece5d47f1bc5e1953fbe0e9931d54dab6d810 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x83abecf7204d5afc1bea5df734f085f2535a9976 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xb2eb5849e2606f99fc492e9add0103c667f806d3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x53c6ca2597711ca7a73b6921faf4031eedf71339 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xd35937ecd47b04a1474f8569f457fc5ac395921a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x6b75f2189f0e11c52e814e09e280eb1a9a8a094a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xb372b5abdb7c2ab8ad9e614be9835a42d0009153 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xf369277650ad6654f25412ea8bfbd5942733babc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x4898cf312fbff8814cab80a8d7f6ee5ad0dc73fb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x5e78afc6c804d4382bede3a0712d210e657e9b4f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x86b211ca7915a0c8d4659dd98242d9e801d88ab4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xb637f7c82fd774c280e23cebc725e7cd807c66d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xd249c43faabc58d6dd4b0a4de598b5a956c5d8d7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x1fbae785ce68b79f7ed4f7b27c3af3ef0e0bc3d4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x3c1376fb8487da57d4ffb263d9d01b578c7b586b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x7b24bed19856f4bb1d4c0421cfb328026cd936bd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x7cf887a863d81e6a483ee947dee05cb51914923c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x588c8cf031809486f015908864ee8699b44017e4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x3987d38a4ff8520a8ef6bcc6f98d6da8bcd69b89 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xde67d05242b18af00b28678db34feec883cc9cd6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x4a5a8b0108f446df7c1c8a459fcfb54e844b7343 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xf6ba006abf768ab2d1b5bba2d22d9f13eb1269d4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x4eefe02fce5b53ca33c7717bbd8ad3c9cb0609f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xaf996125e98b5804c00ffdb4f7ff386307c99a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x7924a818013f39cf800f5589ff1f1f0def54f31f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xc1738d90e2e26c35784a0d3e3d8a9f795074bca4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xda908c0bf14ad0b61ea5ebe671ac59b2ce091cbf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x254aa3a898071d6a2da0db11da73b02b4646078f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x41824081f2e7beb83048bf52465ddd7c8e471da2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xa0c2ce1723b3939f47ad01a293292f2f75dc629d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc42442f6402b68626e791a447d87b35cb1c6236e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x84537db6f6aaa2afdb71f325d14b9f5f7825bef1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x13933689ed2c6c66e83aed64336df14896efb7e2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x039df62583ddc1c5fda75db152b87113d863b6d6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x92c2fc5f306405eab0ff0958f6d85d7f8892cf4d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xcbe856765eeec3fdc505ddebf9dc612da995e593 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc39e83fe4e412a885c0577c08eb53bdb6548004a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xdbac78be00503d10ae0074e5e5873a61fc56647c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc1cd3d0913f4633b43fcddbcd7342bc9b71c676f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x6c4c7f46d9d4ef6bc5c9e155f011ad19fc4ef321 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xb2c86ff752f18499b70e8f642b3421405d50d6e9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x16588709ca8f7b84829b43cc1c5cb7e84a321b16 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xd0a4c8a1a14530c7c9efdad0ba37e8cf4204d230 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xf92f2e3fca01491baba0975264362cc38b1cab7b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x3e6e23198679419cd73bb6376518dcc5168c8260 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x531b6a4b3f962208ea8ed5268c642c84bb29be0b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x553e9c493678d8606d6a5ba284643db2110df823 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xe3170d65018882a336743a9c396c52ea4b9c5563 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x1385fc1fe0418ea0b4fcf7adc61fc7535ab7f80d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x5cd0ad98ba6288ed7819246a1ebc0386c32c314b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x0ad1e922e764df5ab6d636f5d21ecc2e41e827f0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x6b3a3d6ed64faf933a7a4b1bd44b2efba47614ac + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x4ce4a1a593ea9f2e6b2c05016a00a2d300c9ffd8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x0843e0f56b9e7fdc4fb95fabba22a01ef4088f41 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x8323d063b1d12acce4742f1e3ed9bc46d71f4222 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xe30e4dfdbb10949c27501922f845e20cfa579f09 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x7e02ae3f794ebade542c92973eb1c46d7e2e935d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xfa22d298e3b0bc1752e5ef2849cec1149d596674 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x8066ee17156e4184d69277e26fa8cbca3a845edf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x418de8e0ab58abfe916a47821a055c59b9502deb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xfb9caae5a5c0ab91f68542124c05d1efbb97d151 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xb68606a75b117906e06caa0755896ad2b3dd0272 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x6e33c0f5e16b45114679eac217e0c0138cefcd2e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xd64fb39a5681908ad488b487d65f5d8479cb235c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x0217fc17c642d29b890bcf888e21be2378493e01 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x099d23a43da5a8a9282266dbefeaaef958150300 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xd92e0767473d1e3ff11ac036f2b1db90ad0ae55f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x40c547e7fd88f60d94788953b83d9342d8d133c6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x397433498c7befde4b4049b98a7ff081a2c17387 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xf9be03505869d719ba194757943575ed2af001f2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x18c40bb9281a07627ff25cea45b7511f68fd0076 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x270d89e983d9821a418bf193684736414fab78c5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xb125aa15ad943d96e813e4a06d0c34716f897e26 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x813c0decbb1097fff46d0ed6a39fb5f6a83043f4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x9a7ac628ba9f330341486380af729c8975388959 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xf2c9339945bff71dd0bffd3c142164112cd05dc6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x12a4619c0bd9710732fbc458e9baa73df6c3d35f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x96530dac7817f186390b64ba63d13becd079b28d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x18fc1e95adb68b556212ebbad777f3fbb644db98 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xabbeb324b090550ca6d15ec71019915813f54f90 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x86d708404d0db1d97843e66d4ed6b86d11be705b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xbfbba3de6a260c8374f8299c38898312c2d6e9a6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xe945683b3462d2603a18bdfbb19261c6a4f03ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xa1bf0e900fb272089c9fd299ea14bfccb1d1c2c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xe46935ae80e05cdebd4a4008b6ccaa36d2845370 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x3041cbd36888becc7bbcbc0045e3b1f144466f5f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xd31d41dffa3589bb0c0183e46a1eed983a5e5978 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x391e8501b626c623d39474afca6f9e46c2686649 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xd0fc8ba7e267f2bc56044a7715a489d851dc6d78 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x4fd47e5102dfbf95541f64ed6fe13d4ed26d2546 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xe9033c0011f35547fa90d3f8a6ad4b666a590759 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x0c3561d3b72e17378d99684414aa8669daeb8bd0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x14653ce9f406ba7f35a7ffa43c81fa7ecd99c788 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x3204e9734a56a4d7c6f4f5822e14182d9d1a43c4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x43faefd4c0c25e969ac211cd97a4a51e52c729b7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xa652ab3be697c7a01fbdce4d73f8e8acd990251c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x29962083891241aad61ad97bae46d032c9c0c55c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x26bf3601b77be9c31b13b22ebca02914db9c7468 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x0d2edd335982f56662d772b93d86901eb9bd2ff9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xbaed273edd493930711fe88690ebd1f30f7f55ab + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x16033643947bf4d8a1ae37b055edf57cb183106a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xf59abf32c1e8c5d2c6e3faa2131533bbcd466194 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x0312187403bf72b8d2d80729894d6ac3300bd63f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x416fdbc4fb8d4d1f48d0d3778c59dfa5352e9b15 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x5918aca9ae924e6eaaa3d293bb92bdec9ab79338 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x8270e64d22cf13e92c641c4006408c7d7e3ff341 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x16503510c58da73486950b72a12ead3d1d8355dd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x7505159f644ddc5eae21c119e328d0d5bee574b0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xe870bfe4aacb6e234b645e535d26c53790d50e78 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x2e2d190ad4e0d7be9569baebd4d33298379b0502 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xb834093d7e46f7644be45e77281394d31003e866 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xb5a1fd804342cfb679bd8ada75718bc3ec43097e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x9e71e2b14d7e6d30811628ab0965f28e4e2edbce + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xa011da4a0c9261ecf4694bf73a74d113aa261133 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x7ab922c1bfdf7df977c7531c5782074d866f3adc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe2d2050430e341a8f3988e2726e44d9370f8cd3a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xed66ba3ea44425805a085b1ca80d00467b055b38 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x40dade19adc198125ec237a2c48b3408568b2f81 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x166bc40da621d3cb978e24334f844b84ddef25f8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x76bf0abd20f1e0155ce40a62615a90a709a6c3d8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x6948d6c8532c6b0006cb67c6fb9c399792c8ac91 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x4e40cf4a7d8724e5adc2b791bbf9451d1e260b93 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x90908e414d3525e33733d320798b5681508255ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xd6b4cce96ddf8aab2e5750983af9a901f17fbc36 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x4cef551255ec96d89fec975446301b5c4e164c59 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xdd0c6bae8ad5998c358b823df15a2a4181da1b80 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x5e6ff2fa4ca244b6b33c7286d368120822eacc11 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x98efd62b4bfbde6393b18b063c506ce5a77f4810 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x3c5096df639262db0a6cd0172f08709d4161094b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xae31f0e673fc5f33cfc0e9abb426d8051404a7c5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xd10456ce05b9af05c8eede0f93ea8aa80a0daa2f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x065c22a16f6531706681fabbc8df135fe6eb1c2e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x8ab8d851c6b31d8a4d42fd7d3e47b20861b025f2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc0067d751fb1172dbab1fa003efe214ee8f419b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc3d7aa944105d3fafe07fc1822102449c916a8d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x2982d3295a0e1a99e6e88ece0e93ffdfc5c761ae + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc593fe9193b745447e86b45ea0bf62565ee030cc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x88051b0eea095007d3bef21ab287be961f3d8598 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xb31273fd2dfc05e6fd91a3b8a2a681aeb0fbcf48 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xaf7b48ae2f4773fd44f9208cca3db5ae7bfa7e37 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xc2125a452115ff5a300cc2a6ffae99637f6e329d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xb08a8794a5d3ccca3725d92964696858d3201909 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xae99efe6b04bbe5b8b4ad567946fb84b35681abb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x6696710b8e3dc0d844c8b9244767962a4a61ad97 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xcde77ef185a8f886d03b109573cc1dcdcf3cf1f8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x35f5387decce5a234da1a32ca3c9e338a48bcf37 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x4178dd7eb2eb983ba7f7e41648cf91db6be20190 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xb6c8f9490314394cfc6edacb8717bfdc1eb8dab5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x1625fe58cdb3726e5841fb2bb367dde9aaa009b3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xb1ed164c736909ba7ddbc1feb7ced4eaad854a87 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x95faa9a91cd6c1c018e4b1a6fc4c89d4f1695e5d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xa143ccf73c25eec6f38bd1b741043ebea228b8e9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x2e067e0eab7fd31c01473c0f56f3295afb82e461 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xbc83c60e853398d263c1d88899cf5a8b408f9654 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xaf21b0ec0197e63a5c6cc30c8e947eb8165c6212 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x9c84f58bb51fabd18698efe95f5bab4f33e96e8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x202a6012894ae5c288ea824cbc8a9bfb26a49b93 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x744159757cac173a7a3ecf5e97adb10d1a725377 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x127452f3f9cdc0389b0bf59ce6131aa3bd763598 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x2264ba9dc0b257c69eeae7782e8ff608cc65d6a7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x00a59c2d0f0f4837028d47a391decbffc1e10608 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xad6e8f6a34087bddfb03815e2c10e4f7bfd4395b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xd5bb156cb73bfca62f68dc3dff7e5ec4e305b861 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc0d8f259578c985947a050802fb4857261af0bf3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x74f7a360eb36a46b675ea932ea07094a3ace441f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x626761cc5b9fafe4696bf8def4aa015576bb4bef + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc767c0b2e2e56c455fd29f9ee9b6e6f035c71ed4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x625cb959213d18a9853973c2220df7287f1e5b7d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x5ced44f03ff443bbe14d8ea23bc24425fb89e3ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x7138eae57e8a214f7297e5e67bb6e183df3572d5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc7bbec68d12a0d1830360f8ec58fa599ba1b0e9b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x840deeef2f115cf50da625f7368c24af6fe74410 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xda71299ff6bdac31bdcafde52a41d460f17e3ad9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xabebc245a9a47166ecd10933d43817c8ef6fb825 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xb007dda6ca7a57785ce04981c30a1934995a197a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x0de383928e4fcf0f90ad2d6a5ee18eb3b9d16a55 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x0a36df020fe3f132e6557899f272bf3d4591620e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x69c7bd26512f52bf6f76fab834140d13dda673ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x8c9d230d45d6cfee39a6680fb7cb7e8de7ea8e71 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xcb198a55e2a88841e855be4eacaad99422416b33 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x9b371948735f612be19195f5f6e5ebc03839cdaf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xb3709d0e16b618b15ee4bcf82d19b9e7d4100914 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xe426e1305f5e6093864762bf9d2d8b44bc211c59 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x7b9a5bc920610f54881f2f6359007957de504862 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xfb82dd4d657033133eea6e5b7015042984c5825f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x92560c178ce069cc014138ed3c2f5221ba71f58a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xac70bd92f89e6739b3a08db9b6081a923912f73d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x1ebcf8831b93450ea81b0619c5e05b98751c8322 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x470d0d72c975a7f328bd63808bfffd28194b3eb6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xa961f0473da4864c5ed28e00fcc53a3aab056c1b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xa5b6d588ceb3aa1bf543d095038479188f884690 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xb1419a7f9e8c6e434b1d05377e0dbc4154e3de78 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x70c132a2ddeccf0d76cc9b64a749ffe375a79a21 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x6ef7d514d75b5a5a3c500dba1b161a81e842e7a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x1b942ce8bf08290f740b9e825c91e07fcd0bfe75 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x4f122edcd91af8cda38c3a87158afa8687bab57c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xed3fe08bd12f24dad0f1a1e58610644debe374fb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x7bc815ca2c2115f896bb14b31b8196388c05e99b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xd29c2df656b2e4ae6b6817ccc2ebe932fc6a950b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x1f6082db7c8f4b199e17090cd5c8831a1dad1997 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc64f886397988ff16d72123dbe3d46e5bf33ffac + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x0d2c430c6f7ef48ed34bf4aad0ec377e03cc53cf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x2b11a34f52e354ef197f0a2397008699b875ae7e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xde27bdec962a74a72fa1c5ef50bff6f3da083e05 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x7766bdc5ff15d3aceb4d37914963aebaccf3de15 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x5016cd7b785a773f7f3a3ff4035a1e7a76543946 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xf6c4e4f339912541d3f8ed99dba64a1372af5e5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x67ab7dc903a10838a0de8861dfdff3287cf98e5c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x88aaeed1fcfca2eda30749afa9ad45a75c80e292 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x2c8e9a1586ed822f79c0a241e1a4d48e839b3182 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x847165954680b989902e354f34d08b09afab3cd9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x590269935821d760c54b32d31db66ba47d4e53b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x03d70bf9e6afbf8cac09ef0c45f9a00a841c2bed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x8b238f615c1f312d22a65762bcf601a37f1eeec7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x5280d5e63b416277d0f81fae54bb1e0444cabdaa + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xf4e43a4a17d2820c7cf724e46844943931a47894 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x5ab53ee1d50eef2c1dd3d5402789cd27bb52c1bb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xe6ff8b9a37b0fab776134636d9981aa778c4e718 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x44af8d03393e498eec5fcfc7936ebc381f02974d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x4094915f7849b26e8d43dee1f7e3b7b477a0b5bb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc3f5e0d4cdff86e85486cf6bd20cc0884df5f98e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x87428a53e14d24ab19c6ca4939b4df93b8996ca9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x9dbe5dffaeb4ac2e0ac14f8b4e08b3bc55de5232 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xa7bb0d95c6ba0ed0aca70c503b34bc7108589a47 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xbcfac19a0036ada56496316ee5cf388c2af2bf58 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x296b88b607ea3a03c821ca4dc34dd9e7e4efa041 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x019c29d5c97f8cbaa67013e2cf4b6506a5cf183a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x30442fcebbd75a5bb58377c0174d5ce637e297d7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x6c561b446416e1a00e8e93e221854d6ea4171372 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x0fb597d6cfe5be0d5258a7f017599c2a4ece34c7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe9b7057f9b81a0120c09306d35f22859473f18cb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x8deb37b048f4b3c7bd61eca7dfccbef7cba726de + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x455fd3ae52a8ab80f319a1bf912457aa8296695a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe11d03bef391ee0a4b670176e23eb44aad490f12 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe7f850731fed6af4c36cce93eccfbcda0634a030 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xadad4ce0c68f50a19cf5063e0b91d701daab1df1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x5e9bb3d7682a9537db831060176c4247ab80d1ec + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xe9ed60539a8ea7a4da04ebfa524e631b1fd48525 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x0511791eb6fb175a1aaa645114f0f5c8689ec163 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xf3c7b93db3f28580b0fd10365e619eedceb40e76 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x58ecf9cec06bc58fde9280d348f79ed8f3d3046e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xedc7f0dfd9751ef95bb8786a3b130f490743bb0e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x6bcb0ba386e9de0c29006e46b2f01f047ca1806e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc3576f38c32e95e36bbd8d91e6cbe646a3723110 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x8d58e202016122aae65be55694dbce1b810b4072 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x34a43471377dcce420ce8e3ffd9360b2e08fa7b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x766854992bd5363ebeeff0113f5a5795796befab + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x9438a9d1bdeece02ed4431ac59613a128201e0b9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x0a63d3910ffc1529190e80e10855c4216407cc45 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x89084692453ab2305f5f8ac7d70d5efd37a86b8f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xb34a5657988da5b9888952c439756594613507aa + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x05efb437e4e97efea6450321eca8d7585a731369 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc973c86afc23ed731ce1a14d7179003a1601205f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x0f44a1c2b66418f784607d2067fe695703809bff + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x0da6253560822973185297d5f32ff8fa38243afe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xa95b0f5a65a769d82ab4f3e82842e45b8bbaf101 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x886b4f0cb357e0d6ec07b7a3985f346cc17ece7d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x50defb73a76efe5d5d35cf267ffb02dfd6cd96bc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x7aea2e8a3843516afa07293a10ac8e49906dabd1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x8c7080564b5a792a33ef2fd473fba6364d5495e5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x8f81b80d950e5996346530b76aba2962da5c9edb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x7bc0f74d8d94e8e9fdaa40bbc04cc44fb8e0f081 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x09c149c856e6fb6e40aa39209142411b554b1a41 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x7ef0a523c49b1dd07e3593198c5260a95ad7859a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x622270721fb38fde831ab23a8e177665557f6fa9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x15aa01580ae866f9ff4dbe45e06e307941d90c7b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0x4548280ac92507c9092a511c7396cbea78fa9e49 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xe0554a476a092703abdb3ef35c80e0d76d32939f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/ethereum/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x421803da50d3932caa36bd1731d36a0e2af93542 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x258a4b7373f6863db5a17de191e0cebb1e0bbc8a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0x45126b956401daaec92afba2a9953e14b16fb83f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/polygon/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xa3eaa52b505cf61aadcfe21424d43a6847dd6331 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x722bcf6c16dadcc29914e4e64290c46aa1406de8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0x1e1367dcebe168554e82552e0e659a4116926d10 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x4d170f8714367c44787ae98259ce8adb72240067 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xaa97f0689660ea15b7d6f84f2e5250b63f2b381a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xc555d55279023e732ccd32d812114caf5838fd46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xb736330326cf379ecd918dba10614bd63c2713da + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0xe3d4faff3179f0a664a3a84c3e1da3b90e27f186 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/arbitrum/0x50e7b9293aef80c304234e86c84a01be8401c530 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x87dddd2e152bf1955e7e03d9f23a9dcc163eebf6 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0xd9dd34576c7034beb0b11a99afffc49e91011235 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/optimism/0x394a9fcbab8599437d9ec4e5a4a0eb7cb1fd2f69 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xb3adde966b8a1a6f22a04914ee9fe0798e71fc5b + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/base/0xa2d4a8e00daad32acace1a0dd0905f6aaf57e84e + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0x2392ae4ba6daf181ce7343d237b695cdf525e233 + 2024-09-27T19:51:14.628Z 0.8 \ No newline at end of file diff --git a/apps/web/public/tokens-sitemap.xml b/apps/web/public/tokens-sitemap.xml index e727529afef..954e3a4d21d 100644 --- a/apps/web/public/tokens-sitemap.xml +++ b/apps/web/public/tokens-sitemap.xml @@ -2,3182 +2,6842 @@ https://app.uniswap.org/explore/tokens/ethereum/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xdac17f958d2ee523a2206206994597c13d831ec7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x6b175474e89094c44da98b954eedeac495271d0f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x6123b0049f904d730db3c36a31167d9d4121fa6b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xcf0c122c6b73ff809c693db761e7baebe62b6a2e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xfaba6f8e4a5e8ab82f62fe7c39859fa577269be3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x58cb30368ceb2d194740b144eab4c2da8a917dcb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x4c9edd5852cd905f086c759e8383e09bff1e68b3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xaaee1a9723aadb7afa2810263653a34ba2c21c7a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x514910771af9ca656af840dff83e8264ecf986ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x5b7533812759b45c2b44c19e320ba2cd2681b542 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xae78736cd615f374d3085123a210448e74fc6393 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xb9f599ce614feb2e1bbe58f180f370d05b39344e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd31a59c85ae9d8edefec411d448f90841571b89c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x6a7eff1e2c355ad6eb91bebb5ded49257f3fed98 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x576e2bed8f7b46d34016198911cdf9886f78bea7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1258d60b224c0c5cd888d37bbf31aa5fcfb7e870 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x62d0a8458ed7719fdaf978fe5929c6d342b0bfce - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x24fcfc492c1393274b6bcd568ac9e225bec93584 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x27702a26126e0b3702af63ee09ac4d1a084ef628 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd46ba6d942050d489dbd938a2c909a5d5039a161 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xbe9895146f7af43049ca1c1ae358b0541ea49704 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x72f713d11480dcf08b37e1898670e736688d218d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x0001a500a6b18995b03f44bb040a5ffc28e45cb0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x9e9fbde7c7a83c43913bddc8779158f1368f0413 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x5f98805a4e8be255a32880fdec7f6728c6568ba0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x2b591e99afe9f32eaa6214f7b7629768c40eeb39 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1ae7e1d0ce06364ced9ad58225a1705b3e5db92b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x046eee2cc3188071c02bfc1745a6b17c656e3f3d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x84018071282d4b2996272659d9c01cb08dd7327f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x12970e6868f88f6557b76120662c1b3e50a646bf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xaea46a60368a7bd060eec7df8cba43b7ef41ad85 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xc01154b4ccb518232d6bbfc9b9e6c5068b766f82 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x5a98fcbea516cf06857215779fd812ca3bef1b32 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x102c776ddb30c754ded4fdcc77a19230a60d4e4f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x72e4f9f808c49a2a61de9c5896298920dc4eeea9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x467719ad09025fcc6cf6f8311755809d45a5e5f3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf19308f923582a6f7c465e5ce7a9dc1bec6665b1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x710287d1d39dcf62094a83ebb3e736e79400068a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf951e335afb289353dc249e82926178eac7ded78 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf017d3690346eb8234b85f74cee5e15821fee1f4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8c282c35b5e1088bb208991c151182a782637699 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xeaa63125dd63f10874f99cdbbb18410e7fc79dd3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xde342a3e269056fc3305f9e315f4c40d917ba521 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x2dff88a56767223a5529ea5960da7a3f5f766406 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x626e8036deb333b408be468f951bdb42433cbf18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xdd66781d0e9a08d4fbb5ec7bac80b691be27f21d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xb23d80f5fefcddaa212212f028021b41ded428cf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xbaac2b4491727d78d2b78815144570b9f2fe8899 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf8ebf4849f1fa4faf0dff2106a173d3a6cb2eb3a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xb90b2a35c65dbc466b04240097ca756ad2005295 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1614f18fc94f47967a3fbe5ffcd46d4e7da3d787 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf1df7305e4bab3885cab5b1e4dfc338452a67891 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x91fbb2503ac69702061f1ac6885759fc853e6eae - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xa9e8acf069c58aec8825542845fd754e41a9489a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x2c95d751da37a5c1d9c5a7fd465c1d50f3d96160 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xe453c3409f8ad2b1fe1ed08e189634d359705a5b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x89d584a1edb3a70b3b07963f9a3ea5399e38b136 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x4507cef57c46789ef8d1a19ea45f4216bae2b528 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd1d2eb1b1e90b638588728b4130137d262c87cae - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xe92344b4edf545f3209094b192e46600a19e7c2d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8a0a9b663693a22235b896f70a229c4a22597623 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1bbe973bef3a977fc51cbed703e8ffdefe001fed - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xa41d2f8ee4f47d3b860a149765a7df8c3287b7f0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x761d38e5ddf6ccf6cf7c55759d5210750b5d60f3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xc18360217d8f7ab5e7c516566761ea12ce7f9d72 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xe28b3b32b6c345a34ff64674606124dd5aceca30 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x168e209d7b2f58f1f24b8ae7b7d35e662bbf11cc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xb131f4a55907b10d1f0a50d8ab8fa09ec342cd74 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x3472a5a71965499acd81997a54bba8d852c6e53d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x19efa7d0fc88ffe461d1091f8cbe56dc2708a84f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x14fee680690900ba0cccfc76ad70fd1b95d10e16 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x3c3a81e81dc49a522a592e7622a7e711c06bf354 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xa1290d69c65a6fe4df752f95823fae25cb99e5a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x92f419fb7a750aed295b0ddf536276bf5a40124f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x2c06ba9e7f0daccbc1f6a33ea67e85bb68fbee3a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x3d658390460295fb963f54dc0899cfb1c30776df - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8e870d67f660d95d5be530380d0ec0bd388289e1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x853d955acef822db058eb8505911ed77f175b99e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1294f4183763743c7c9519bec51773fb3acd78fd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x4e15361fd6b4bb609fa63c81a2be19d873717870 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x695d38eb4e57e0f137e36df7c1f0f2635981246b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x40a7df3df8b56147b781353d379cb960120211d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xaaef88cea01475125522e117bfe45cf32044e238 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x163f8c2467924be0ae7b5347228cabf260318753 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x30672ae2680c319ec1028b69670a4a786baa0f35 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xc944e90c64b2c07662a292be6244bdf05cda44a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x15e6e0d4ebeac120f9a97e71faa6a0235b85ed12 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x7d225c4cc612e61d26523b099b0718d03152edef - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x82af49447d8a07e3bd95bd0d56f35241523fbab1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xaf88d065e77c8cc2239327c5edb3a432268e5831 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xff970a61a04b1ca14834a43f5de4533ebddb5cc8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x912ce59144191c1204e64559fe8253a0e49e6548 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x5979d7b546e38e414f7e9822514be443a4800529 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x35751007a407ca6feffe80b3cb397736d2cf4dbe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xeb466342c4d449bc9f53a865d5cb90586f405215 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x0c880f6761f1af8d9aa9c466984b80dab9a8c9e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf97f4df75117a78c1a5a0dbb814af92458539fb4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x9623063377ad1b27544c965ccd7342f7ea7e88c7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x539bde0d7dbd336b79148aa742883198bbf60342 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3082cc23568ea640225c2467653db90e9250aaa0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x18c11fd286c5ec11c3b683caa813b77f5163a122 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x289ba1701c2f088cf0faf8b3705246331cb8a839 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x4cb9a7ae498cedcbb5eae9f25736ae7d428c9d66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x00cbcf7b3d37844e44b888bc747bdd75fcf4e555 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd79bb960dc8a206806c3a428b31bca49934d18d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3096e7bfd0878cc65be71f8899bc4cfb57187ba3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x13ad51ed4f1b7e9dc168d8a00cb3f4ddd85efa60 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x4e352cf164e64adcbad318c3a1e222e9eba4ce42 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x11cdb42b0eb46d95f990bedd4695a6e3fa034978 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xba5ddd1f9d7f570dc94a51479a000e3bce967196 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xc8ccbd97b96834b976c995a67bf46e5754e2c48e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd07d35368e04a839dee335e213302b21ef14bb4a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x323665443cef804a3b5206103304bd4872ea4253 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x83d6c8c06ac276465e4c92e7ac8c23740f435140 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x87aaffdf26c6885f6010219208d5b161ec7609c0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x1b8d516e2146d7a32aca0fcbf9482db85fd42c3a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xafccb724e3aec1657fc9514e3e53a0e71e80622d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x4425742f1ec8d98779690b5a3a6276db85ddc01a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3419875b4d3bca7f3fdda2db7a476a79fd31b4fe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3b60ff35d3f7f62d636b067dd0dc0dfdad670e4e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x58b9cb810a68a7f3e1e4f8cb45d1b9b3c79705e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xfa5ed56a203466cbbc2430a43c66b9d8723528e7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x95146881b86b3ee99e63705ec87afe29fcc044d9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x088cd8f5ef3652623c22d48b1605dcfe860cd704 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xbfd5206962267c7b4b4a8b3d76ac2e1b2a5c4d5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x6daf586b7370b14163171544fca24abcc0862ac5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x9d2f299715d94d8a7e6f5eaa8e654e8c74a988a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x580e933d90091b9ce380740e3a4a39c67eb85b4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x655a6beebf2361a19549a99486ff65f709bd2646 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x9e64d3b9e8ec387a9a58ced80b71ed815f8d82b5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x2297aebd383787a160dd0d9f71508148769342e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x6694340fc020c5e6b96567843da2df01b2ce1eb6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x772598e9e62155d7fdfe65fdf01eb5a53a8465be - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x431402e8b9de9aa016c743880e04e517074d8cec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd74f5255d557944cf7dd0e45ff521520002d5748 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x6fd58f5a2f3468e35feb098b5f59f04157002407 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x561877b6b3dd7651313794e5f2894b2f18be0766 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf9ca0ec182a94f6231df9b14bd147ef7fb9fa17c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd77b108d4f6cefaa0cae9506a934e825becca46e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd56734d7f9979dd94fae3d67c7e928234e71cd4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf1264873436a0771e440e2b28072fafcc5eebd01 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x5575552988a3a80504bbaeb1311674fcfd40ad4b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x0341c0c0ec423328621788d4854119b97f44e391 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x764bfc309090e7f93edce53e5befa374cdcb7b8e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xaaa6c1e32c55a7bfa8066a6fae9b42650f262418 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x9e20461bc2c4c980f62f1b279d71734207a6a356 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x7fb7ede54259cb3d4e1eaf230c7e2b1ffc951e9a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3a18dcc9745edcd1ef33ecb93b0b6eba5671e7ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x000000000026839b3f4181f2cf69336af6153b99 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x8b0e6f19ee57089f7649a455d89d7bc6314d04e8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x31c91d8fb96bff40955dd2dbc909b36e8b104dde - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x25d887ce7a35172c62febfd67a1856f20faebb00 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd4d42f0b6def4ce0383636770ef773390d85c61a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf8388c2b6edf00e2e27eef5200b1befb24ce141d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x619c82392cb6e41778b7d088860fea8447941f4c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x94025780a1ab58868d9b2dbbb775f44b32e8e6e5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xad4b9c1fbf4923061814dd9d5732eb703faa53d4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd7a892f28dedc74e6b7b33f93be08abfc394a360 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3269a3c00ab86c753856fd135d97b87facb0d848 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x4568ca00299819998501914690d6010ae48a59ba - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x21e60ee73f17ac0a411ae5d690f908c3ed66fe12 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xd3188e0df68559c0b63361f6160c57ad88b239d8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x2b41806cbf1ffb3d9e31a9ece6b738bf9d6f645f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf19547f9ed24aa66b03c3a552d181ae334fbb8db - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x35e6a59f786d9266c7961ea28c7b768b33959cbb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x59a729658e9245b0cf1f8cb9fb37945d2b06ea27 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xb56c29413af8778977093b9b4947efeea7136c36 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x43ab8f7d2a8dd4102ccea6b438f6d747b1b9f034 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x1d987200df3b744cfa9c14f713f5334cb4bc4d5d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3404149e9ee6f17fb41db1ce593ee48fbdcd9506 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x080f6aed32fc474dd5717105dba5ea57268f46eb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xb5a628803ee72d82098d4bcaf29a42e63531b441 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x1622bf67e6e5747b81866fe0b85178a93c7f86e3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x7dd747d63b094971e6638313a6a2685e80c7fb2e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xa2f9ecf83a48b86265ff5fd36cdbaaa1f349916c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x17a8541b82bf67e10b0874284b4ae66858cb1fd5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xbcd4d5ac29e06e4973a1ddcd782cd035d04bc0b7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x42069d11a2cc72388a2e06210921e839cfbd3280 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xbbea044f9e7c0520195e49ad1e561572e7e1b948 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xe85b662fe97e8562f4099d8a1d5a92d4b453bf30 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x3d9907f9a368ad0a51be60f7da3b97cf940982d8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x4e51ac49bc5e2d87e0ef713e9e5ab2d71ef4f336 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x4200000000000000000000000000000000000006 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x7f5c764cbc14f9669b88837ca1490cca17c31607 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x4200000000000000000000000000000000000042 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x0b2c639c533813f4aa9d7837caf62653d097ff85 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x1f32b1c2345538c0c6f582fcb022739c4a194ebb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x68f180fcce6836688e9084f035309e29bf0a2095 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x94b008aa00579c1307b0ef2c499ad98a8ce58e58 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xdc6ff44d5d932cbd77b52e5612ba0529dc6226f1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x8700daec35af8ff88c16bdf0418774cb3d7599b4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x920cf626a271321c151d027030d5d08af699456b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x6c84a8f1c29108f47a79964b5fe888d4f4d0de40 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x9e1028f5f1d5ede59748ffcee5532509976840e0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xeb466342c4d449bc9f53a865d5cb90586f405215 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x350a791bfc2c21f9ed5d10980dad2e2638ffa7f6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x17aabf6838a6303fc6e9c5a227dc1eb6d95c829a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xf467c7d5a4a9c4687ffc7986ac6ad5a4c81e1404 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x76fb31fb4af56892a25e32cfc43de717950c9278 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xc5b001dc33727f8f26880b184090d3e252470d45 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x9560e827af36c94d2ac33a39bce1fe78631088db - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x9bcef72be871e61ed4fbbc7630889bee758eb81d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x50c5725949a6f0c72e6c4a641f24049a917db0cb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xf98dcd95217e15e05d8638da4c91125e59590b07 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x4b03afc91295ed778320c2824bad5eb5a1d852dd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xc40f949f8a4e094d1b49a23ea9241d289b7b2819 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x323665443cef804a3b5206103304bd4872ea4253 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x50bce64397c75488465253c0a034b8097fea6578 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x296f55f8fb28e498b858d0bcda06d955b2cb3f97 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x2598c30330d5771ae9f983979209486ae26de875 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x0994206dfe8de6ec6920ff4d779b0d950605fb53 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xc3248a1bd9d72fa3da6e6ba701e58cbf818354eb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x6fd9d7ad17242c41f7131d257212c54a0e816691 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x14778860e937f509e651192a90589de711fb88a9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xdfa46478f9e5ea86d57387849598dbfb2e964b02 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x9b88d293b7a791e40d36a39765ffd5a1b9b5c349 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x3eb398fec5f7327c6b15099a9681d9568ded2e82 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x217d47011b23bb961eb6d93ca9945b7501a5bb11 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xbfd5206962267c7b4b4a8b3d76ac2e1b2a5c4d5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x1cef2d62af4cd26673c7416957cc4ec619a696a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x9fd22a17b4a96da3f83797d122172c450381fb88 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0xaddb6a0412de1ba0f936dcaeb8aaa24578dcf3b2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x2791bca1f2de4661ed88a30c99a7a9449aa84174 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x7ceb23fd6bc0add59e62ac25578270cff1b9f619 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3c499c542cef5e3811e1192ce70d8cc03d5c3359 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xc2132d05d31c914a87c6611c10748aeb04b58e8f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x53e0bca35ec356bd5dddfebbd1fc0fd03fabad39 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x61299774020da444af134c82fa83e3810b309991 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xd6df932a45c0f255f85145f286ea0b292b21c90b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x2ad2934d5bfb7912304754479dd1f096d5c807da - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xc3c7d422809852031b44ab29eec9f1eff2a58756 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x750e4c4984a9e0f12978ea6742bc1c5d248f40ed - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x111111517e4929d3dcbdfa7cce55d30d4b6bc4d6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xd0258a3fd00f38aa8090dfee343f10a9d4d30d3f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x430ef9263e76dae63c84292c3409d61c598e9682 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xb33eaad8d922b1083446dc23f610c2567fb5180f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xdc3326e71d45186f113a2f448984ca0e8d201995 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x311434160d7537be358930def317afb606c0d737 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x0b3f868e0be5597d5db7feb59e1cadbb0fdda50a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe3f2b1b2229c0333ad17d03f179b87500e7c5e01 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xac0f66379a6d7801d7726d5a943356a172549adb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xf88332547c680f755481bf489d890426248bb275 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe5417af564e4bfda1c483642db72007871397896 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe261d618a959afffd53168cd07d12e37b26761db - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe0b52e49357fd4daf2c15e02058dce6bc0057db4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xbbba073c31bf03b8acf7c28ef0738decf3695683 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe238ecb42c424e877652ad82d8a939183a04c35f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3b56a704c01d650147ade2b8cee594066b3f9421 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x5fe2b58c013d7601147dcdd68c143a77499f5531 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x172370d5cd63279efa6d502dab29171933a610af - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x53df32548214f51821cf1fe4368109ac5ddea1ff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xff76c0b48363a7c7307868a81548d340049b0023 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x6f8a06447ff6fcf75d803135a7de15ce88c1d4ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x50b728d8d964fd00c2d0aad81718b71311fef68a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xd93f7e271cb87c23aaa73edc008a79646d1f9912 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x200c234721b5e549c3693ccc93cf191f90dc2af9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x11cd37bb86f65419713f30673a480ea33c826872 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x8a16d4bf8a0a716017e8d2262c4ac32927797a2f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xa1c57f48f0deb89f569dfbe6e2b7f46d33606fd4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x190eb8a183d22a4bdf278c6791b152228857c033 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x2f6f07cdcf3588944bf4c42ac74ff24bf56e7590 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x235737dbb56e8517391473f7c964db31fa6ef280 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x0b220b82f3ea3b7f6d9a1d8ab58930c064a2b5bf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x8bff1bd27e2789fe390acabc379c380a83b68e84 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xb58458c52b6511dc723d7d6f3be8c36d7383b4a8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x323665443cef804a3b5206103304bd4872ea4253 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x2760e46d9bb43dafcbecaad1f64b93207f9f0ed7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x18ec0a6e18e5bc3784fdd3a3634b31245ab704f6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x431d5dff03120afa4bdf332c61a6e1766ef37bdb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x6f7c932e7684666c9fd1d44527765433e01ff61d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xeee3371b89fc43ea970e908536fcddd975135d8a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe5b49820e5a1063f6f4ddf851327b5e8b2301048 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xaa3717090cddc9b227e49d0d84a28ac0a996e6ff - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x62a872d9977db171d9e213a5dc2b782e72ca0033 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x381caf412b45dac0f62fbeec89de306d3eabe384 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe0bceef36f3a6efdd5eebfacd591423f8549b9d5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x23d29d30e35c5e8d321e1dc9a8a61bfd846d4c5c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x282d8efce846a88b159800bd4130ad77443fa1a1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x74dd45dd579cad749f9381d6227e7e02277c944b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x714db550b574b3e927af3d93e26127d15721d4c2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xfa68fb4628dff1028cfec22b4162fccd0d45efb6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe631dabef60c37a37d70d3b4f812871df663226f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xdb725f82818de83e99f1dac22a9b5b51d3d04dd4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3c59798620e5fec0ae6df1a19c6454094572ab92 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x0d0b8488222f7f83b23e365320a4021b12ead608 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xa380c0b01ad15c8cf6b46890bddab5f0868e87f3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x8a953cfe442c5e8855cc6c61b1293fa648bae472 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x45c32fa6df82ead1e2ef74d17b76547eddfaff89 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x11cd72f7a4b699c67f225ca8abb20bc9f8db90c7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x0c9c7712c83b3c70e7c5e11100d33d9401bdf9dd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x77a6f2e9a9e44fd5d5c3f9be9e52831fc1c3c0a0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xbfc70507384047aa74c29cdc8c5cb88d0f7213ac - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xfcb54da3f4193435184f3f647467e12b50754575 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x9a6a40cdf21a0af417f1b815223fd92c85636c58 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe111178a87a3bff0c8d18decba5798827539ae99 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x82617aa52dddf5ed9bb7b370ed777b3182a30fd1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x2ab0e9e4ee70fff1fb9d67031e44f6410170d00e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xa486c6bc102f409180ccb8a94ba045d39f8fc7cb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xc4a206a306f0db88f98a3591419bc14832536862 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xf0059cc2b3e980065a906940fbce5f9db7ae40a7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x16eccfdbb4ee1a85a33f3a9b21175cd7ae753db4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x553d3d295e0f695b9228246232edf400ed3560b5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x14af1f2f02dccb1e43402339099a05a5e363b83c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x7bdf330f423ea880ff95fc41a280fd5ecfd3d09f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x8505b9d2254a7ae468c0e9dd10ccea3a837aef5c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe2aa7db6da1dae97c5f5c6914d285fbfcc32a128 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xb7b31a6bc18e48888545ce79e83e06003be70930 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x1631244689ec1fecbdd22fb5916e920dfc9b8d30 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xf6372cdb9c1d3674e83842e3800f2a62ac9f3c66 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x692ac1e363ae34b6b489148152b12e2785a3d8d6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x0266f4f08d82372cf0fcbccc0ff74309089c74d1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x7fbc10850cae055b27039af31bd258430e714c62 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xa3fa99a148fa48d14ed51d610c367c61876997f1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x9dbfc1cbf7a1e711503a29b4b5f9130ebeccac96 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x236aa50979d5f3de3bd1eeb40e81137f22ab794b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xf86df9b91f002cfeb2aed0e6d05c4c4eaef7cf02 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4200000000000000000000000000000000000006 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x6921b130d297cc43754afba22e5eac0fbf8db75b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x5babfc2f240bc5de90eb7e19d789412db1dec402 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x532f27101965dd16442e59d40670faf5ebb142e4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4ed4e862860bed51a9570b96d89af5e1b0efefed - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x0d97f261b1e88845184f678e2d1e7a98d9fd38de - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8129b94753f22ec4e62e2c4d099ffe6773969ebc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x3f14920c99beb920afa163031c4e47a3e03b3e4a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x940181a94a35a4569e4529a3cdfb74e38fd98631 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x3419875b4d3bca7f3fdda2db7a476a79fd31b4fe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xa067436db77ab18b1a315095e4b816791609897c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xafb89a09d82fbde58f18ac6437b3fc81724e4df6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x489fe42c267fe0366b16b0c39e7aeef977e841ef - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xdc46c1e93b71ff9209a0f8076a9951569dc35855 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x91f45aa2bde7393e0af1cc674ffe75d746b93567 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x236aa50979d5f3de3bd1eeb40e81137f22ab794b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xf6e932ca12afa26665dc4dde7e27be02a7c02e50 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x524d524b4c9366be706d3a90dcf70076ca037ae3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x5b5dee44552546ecea05edea01dcd7be7aa6144a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x2598c30330d5771ae9f983979209486ae26de875 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xfa980ced6895ac314e7de34ef1bfae90a5add21b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x469fda1fb46fcb4befc0d8b994b516bd28c87003 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4e496c0256fb9d4cc7ba2fdf931bc9cbb7731660 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x27d2decb4bfc9c76f0309b8e88dec3a601fe25a8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xbfd5206962267c7b4b4a8b3d76ac2e1b2a5c4d5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x9e1028f5f1d5ede59748ffcee5532509976840e0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x3c3aa127e6ee3d2f2e432d0184dd36f2d2076b52 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xba5e6fa2f33f3955f0cef50c63dcc84861eab663 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x97c806e7665d3afd84a8fe1837921403d59f3dcc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8ee73c484a26e0a5df2ee2a4960b789967dd0415 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x00e57ec29ef2ba7df07ad10573011647b2366f6d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8f019931375454fe4ee353427eb94e2e0c9e0a8c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x93e6407554b2f02640ab806cd57bd83e848ec65d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x55d398326f99059ff775485246999027b3197955 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x2170ed0880ac9a755fd29b2688956bd959f933f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xfdc66a08b0d0dc44c17bbd471b88f49f50cdd20f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xe9e7cea3dedca5984780bafc599bd69add087d56 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xfa54ff1a158b5189ebba6ae130ced6bbd3aea76e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x570a5d26f7765ecb712c0924e4de545b89fd43df - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x47c454ca6be2f6def6f32b638c80f91c9c3c5949 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xad86d0e9764ba90ddd68747d64bffbd79879a238 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xd691d9a68c887bdf34da8c36f63487333acfd103 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x1294f4183763743c7c9519bec51773fb3acd78fd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xb04906e95ab5d797ada81508115611fee694c2b3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x111111111117dc0aa78b770fa6a738034120c302 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xcc42724c6683b7e57334c4e856f4c9965ed682bd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x90c97f71e18723b0cf0dfa30ee176ab653e89f40 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x2b72867c32cf673f7b02d208b26889fed353b1f8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x031b41e504677879370e9dbcf937283a8691fa7f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x1ce0c2827e2ef14d5c4f29a091d735a204794041 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xcf3bb6ac0f6d987a5727e2d15e39c2d6061d5bec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x8ff795a6f4d97e7887c79bea79aba5cc76444adf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x2dff88a56767223a5529ea5960da7a3f5f766406 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x003d87d02a2a01e9e8a20f507c83e15dd83a33d1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x4b0f1812e5df2a09796481ff14017e6005508003 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xbf5140a22578168fd562dccf235e5d43a02ce9b1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xca1c644704febf4ab81f85daca488d1623c28e63 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x51e72dd1f2628295cc2ef931cb64fdbdc3a0c599 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xbbca42c60b5290f2c48871a596492f93ff0ddc82 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x555296de6a86e72752e5c5dc091fe49713aa145c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x0808bf94d57c905f1236212654268ef82e1e594e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x8457ca5040ad67fdebbcc8edce889a335bc0fbfb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xcebef3df1f3c5bfd90fde603e71f31a53b11944d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x90ed8f1dc86388f14b64ba8fb4bbd23099f18240 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x9840652dc04fb9db2c43853633f0f62be6f00f98 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xba2ae424d960c26247dd6c32edc70b295c744c43 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x0782b6d8c4551b9760e74c0545a9bcd90bdc41e5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xbe2b6c5e31f292009f495ddbda88e28391c9815e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x8f0528ce5ef7b51152a59745befdd91d97091d2f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xffeecbf8d7267757c2dc3d13d730e97e15bfdf7f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x0eb3a705fc54725037cc9e008bdede697f62f335 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xf21768ccbc73ea5b6fd3c687208a7c2def2d966e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x0000028a2eb8346cd5c0267856ab7594b7a55308 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x76a797a59ba2c17726896976b7b3747bfd1d220f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xc79d1fd14f514cd713b5ca43d288a782ae53eab2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xad29abb318791d579433d831ed122afeaf29dcfe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x3203c9e46ca618c8c1ce5dc67e7e9d75f5da2377 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xdb021b1b247fe2f1fa57e0a87c748cc1e321f07f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x7083609fce4d1d8dc0c979aab8c869ea2c873402 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xc5f0f7b66764f6ec8c8dff7ba683102295e16409 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xe29142e14e52bdfbb8108076f66f49661f10ec10 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xb0d502e938ed5f4df2e681fe6e419ff29631d62b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x6730f7a6bbb7b9c8e60843948f7feb4b6a17b7f7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x1613957159e9b0ac6c80e824f7eea748a32a0ae2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x471ece3750da237f93b8e339c536989b8978a438 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x765de816845861e75a25fca122bb6898b8b1282a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x66803fb87abd4aac3cbb3fad7c3aa01f6f3fb207 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x37f750b7cc259a2f741af45294f6a16572cf5cad - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0xd71ffd0940c920786ec4dbb5a12306669b5b81ef - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0xe8537a3d056da446677b9e9d6c5db704eaab4787 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x02de4766c272abc10bc88c220d214a26960a7e92 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0xceba9300f2b948710d2653dd7b07f33a8b32118c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0xc16b81af351ba9e64c1a069e3ab18c244a1e3049 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x728f30fa2f100742c7949d1961804fa8e0b1387d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x41ea5d41eeacc2d5c4072260945118a13bb7ebce - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf21661d0d1d76d3ecb8e1b9f1c923dbfffae4097 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xb0ecc6ac0073c063dcfc026ccdc9039cae2998e1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x00f932f0fe257456b32deda4758922e56a4f4b42 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xa4af354d466e8a68090dd9eb2cb7caf162f4c8c2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xba50933c268f567bdc86e1ac131be072c6b0b71a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd29da236dd4aac627346e1bba06a619e8c22d7c5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1bfce574deff725a3f483c334b790e25c8fa9779 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x9e18d5bab2fa94a6a95f509ecb38f8f68322abd3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xbf5495efe5db9ce00f80364c8b423567e58d2110 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x065b4e5dfd50ac12a81722fd0a0de81d78ddf7fb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x57e114b691db790c35207b2e685d4a43181e6061 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x0b7f0e51cd1739d6c96982d55ad8fa634dd43a9c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xc56c7a0eaa804f854b536a5f3d5f49d2ec4b12b8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x594daad7d77592a2b97b725a7ad59d7e188b5bfa - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8355dbe8b0e275abad27eb843f3eaf3fc855e525 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x2a961d752eaa791cbff05991e4613290aec0d9ac - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x38e68a37e401f7271568cecaac63c6b1e19130b4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1131d427ecd794714ed00733ac0f851e904c8398 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1495bc9e44af1f8bcb62278d2bec4540cf0c05ea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x808507121b80c02388fad14726482e061b8da827 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x44971abf0251958492fee97da3e5c5ada88b9185 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x320623b8e4ff03373931769a31fc52a4e78b5d70 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x6e5970dbd6fc7eb1f29c6d2edf2bc4c36124c0c1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd40c688da9df74e03566eaf0a7c754ed98fbb8cc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8afe4055ebc86bd2afb3940c0095c9aca511d852 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x9ce84f6a69986a83d92c324df10bc8e64771030f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xbe4d9c8c638b5f0864017d7f6a04b66c42953847 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x68bbed6a47194eff1cf514b50ea91895597fc91e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x69420e3a3aa9e17dea102bb3a9b3b73dcddb9528 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x7420b4b9a0110cdc71fb720908340c03f9bc03ec - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x03aa6298f1370642642415edc0db8b957783e8d6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd533a949740bb3306d119cc777fa900ba034cd52 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf14dd7b286ce197019cba54b189d2b883e70f761 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xa35923162c49cf95e6bf26623385eb431ad920d3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8cefbeb2172a9382753de431a493e21ba9694004 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x120a3879da835a5af037bb2d1456bebd6b54d4ba - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x69457a1c9ec492419344da01daf0df0e0369d5d0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf6ce4be313ead51511215f1874c898239a331e37 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x73d7c860998ca3c01ce8c808f5577d94d545d1b4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xeff49b0f56a97c7fd3b51f0ecd2ce999a7861420 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x236501327e701692a281934230af0b6be8df3353 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x5026f006b85729a8b14553fae6af249ad16c9aab - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x66761fa41377003622aee3c7675fc7b5c1c2fac5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x9f9c8ec3534c3ce16f928381372bfbfbfb9f4d24 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd8c978de79e12728e38aa952a6cb4166f891790f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x7122985656e38bdc0302db86685bb972b145bd3c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x582d872a1b094fc48f5de31d3b73f2d9be47def1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x504624040e0642921c2c266a9ac37cafbd8cda4e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xc548e90589b166e1364de744e6d35d8748996fe8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x4c11249814f11b9346808179cf06e71ac328c1b5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x423f4e6138e475d85cf7ea071ac92097ed631eea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8390a1da07e376ef7add4be859ba74fb83aa02d5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xf94e7d0710709388bce3161c32b4eea56d3f91cc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xaa95f26e30001251fb905d264aa7b00ee9df6c18 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x2416092f143378750bb29b79ed961ab195cceea5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x6c84a8f1c29108f47a79964b5fe888d4f4d0de40 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x71eeba415a523f5c952cc2f06361d5443545ad28 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x88a269df8fe7f53e590c561954c52fccc8ec0cfb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x429fed88f10285e61b12bdf00848315fbdfcc341 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xb299751b088336e165da313c33e3195b8c6663a6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf0a479c9c3378638ec603b8b6b0d75903902550b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xb59c8912c83157a955f9d715e556257f432c35d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xba0dda8762c24da9487f5fa026a9b64b695a07ea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xc24a365a870821eb83fd216c9596edd89479d8d7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xa586b3b80d7e3e8d439e25fbc16bc5bcee3e2c85 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xef04804e1e474d3f9b73184d7ef5d786f3fce930 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x2e9a6df78e42a30712c10a9dc4b1c8656f8f2879 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x13a7dedb7169a17be92b0e3c7c2315b46f4772b3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x1dd6b5f9281c6b4f043c02a83a46c2772024636c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xc5102fe9359fd9a28f877a67e36b0f050d81a3cc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xf525e73bdeb4ac1b0e741af3ed8a8cbb43ab0756 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xe4177c1400a8eee1799835dcde2489c6f0d5d616 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xed5740209fcf6974d6f3a5f11e295b5e468ac27c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0xe10d4a4255d2d35c9e23e2c4790e073046fbaf5c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x10398abc267496e49106b07dd6be13364d10dc71 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x2218a117083f5b482b0bb821d27056ba9c04b1d3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x395ae52bb17aef68c2888d941736a71dc6d4e125 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x9a601c5bb360811d96a23689066af316a30c3027 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xbac3368b5110f3a3dda8b5a0f7b66edb37c47afe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x1d3c629ca5c1d0ab3bdf74600e81b4145615df8e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xe9c21de62c5c5d0ceacce2762bf655afdceb7ab3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x658cda444ac43b0a7da13d638700931319b64014 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3d2bd0e15829aa5c362a4144fdf4a1112fa29b5c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3fb83a9a2c4408909c058b0bfe5b4823f54fafe2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x00e5646f60ac6fb446f621d146b6e1886f002905 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x12a4cebf81f8671faf1ab0acea4e3429e42869e7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x9ff62d1fc52a907b6dcba8077c2ddca6e6a9d3e1 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0xc61f39418cd27820b5d4e9ba4a7197eefaeb8b05 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x15b7c0c907e4c6b9adaaaabc300c08991d6cea05 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x7f67639ffc8c93dd558d452b8920b28815638c44 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x276c9cbaa4bdf57d7109a41e67bd09699536fa3d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x041fdf3f472d2c8a7ecc458fc3b7f543e6c57ef7 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x3c281a39944a2319aa653d81cfd93ca10983d234 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x96419929d7949d6a801a6909c145c8eef6a40431 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xfea9dcdc9e23a9068bf557ad5b186675c61d33ea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xdb6e0e5094a25a052ab6845a9f1e486b9a9b3dde - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xcde172dc5ffc46d228838446c57c1227e0b82049 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xff0c532fdb8cd566ae169c1cb157ff2bdc83e105 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x9a26f5433671751c3276a065f57e5a02d2817973 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x3636a7734b669ce352e97780df361ce1f809c58c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x50c5725949a6f0c72e6c4a641f24049a917db0cb - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xe3086852a4b125803c815a158249ae468a3254ca - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xbeb0fd48c2ba0f1aacad2814605f09e08a96b94e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xbc45647ea894030a4e9801ec03479739fa2485f0 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x768be13e1680b5ebe0024c42c896e3db59ec0149 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x928a6a9fc62b2c94baf2992a6fba4715f5bb0066 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xbf4db8b7a679f89ef38125d5f84dd1446af2ea3b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xed899bfdb28c8ad65307fa40f4acab113ae2e14c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x1b6a569dd61edce3c383f6d565e2f79ec3a12980 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x76734b57dfe834f102fb61e1ebf844adf8dd931e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4621b7a9c75199271f773ebd9a499dbd165c3191 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xaf07d812d1dcec20bf741075bc18660738d226dd - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x7f12d13b34f5f4f0a9449c16bcd42f0da47af200 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x55a6f6cb50db03259f6ab17979a4891313be2f45 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x968d6a288d7b024d5012c0b25d67a889e4e3ec19 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x7a8a5012022bccbf3ea4b03cd2bb5583d915fb1a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xcde90558fc317c69580deeaf3efc509428df9080 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x0028e1e60167b48a938b785aa5292917e7eaca8b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x76e7447bafa3f0acafc9692629b1d1bc937ca15d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x15ac90165f8b45a80534228bdcb124a011f62fee - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4045b33f339a3027af80013fb5451fdbb01a4492 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xddf98aad8180c3e368467782cd07ae2e3e8d36a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x698dc45e4f10966f6d1d98e3bfd7071d8144c233 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x3c8665472ec5af30981b06b4e0143663ebedcc1e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x18a8bd1fe17a1bb9ffb39ecd83e9489cfd17a022 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xba0dda8762c24da9487f5fa026a9b64b695a07ea - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x13741c5df9ab03e7aa9fb3bf1f714551dd5a5f8a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xebff2db643cf955247339c8c6bcd8406308ca437 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xfadb26be94c1f959f900bf88cd396b3e803481d6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x52c2b317eb0bb61e650683d2f287f56c413e4cf6 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x38d513ec43dda20f323f26c7bef74c5cf80b6477 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x33ad778e6c76237d843c52d7cafc972bb7cf8729 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x290814ad0fbd2b935f34d7b40306102313d4c63e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x5e432eecd01c12ee7071ee9219c2477a347da192 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xbdf5bafee1291eec45ae3aadac89be8152d4e673 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xff62ddfa80e513114c3a0bf4d6ffff1c1d17aadf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8c81b4c816d66d36c4bf348bdec01dbcbc70e987 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x6b82297c6f1f9c3b1f501450d2ee7c37667ab70d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x42069babe14fb1802c5cb0f50bb9d2ad6fef55e2 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x72499bddb67f4ca150e1f522ca82c87bc9fb18c8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x0578d8a44db98b23bf096a382e016e29a5ce0ffe - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8fe815417913a93ea99049fc0718ee1647a2a07c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x7d12aeb5d96d221071d176980d23c213d88d9998 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xb166e8b140d35d9d8226e40c09f757bac5a4d87d - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8853f0c059c27527d33d02378e5e4f6d5afb574a - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xf3c052f2baab885c610a748eb01dfbb643ba835b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xcd1cffa8ebc66f1a2cf7675b48ba955ffcb82d8e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xde7a416ac821c77478340eebaa21b68297025ef3 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x2da56acb9ea78330f947bd57c54119debda7af71 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8972ab69d499b5537a31576725f0af8f67203d38 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x88faea256f789f8dd50de54f9c807eef24f71b16 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x42069de48741db40aef864f8764432bbccbd0b69 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x9a27c6759a6de0f26ac41264f0856617dec6bc3f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xfaa4f3bcfc87d791e9305951275e0f62a98bcb10 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xfd9fa4f785331ce88b5af8994a047ba087c705d8 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x21eceaf3bf88ef0797e3927d855ca5bb569a47fc - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x7d9ce55d54ff3feddb611fc63ff63ec01f26d15f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4229c271c19ca5f319fb67b4bc8a40761a6d6299 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x80f45eacf6537498ecc660e4e4a2d2f99e195cf4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x1a475d06d967aeb686c98de80d079d72097aeacf - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x4fb9b20dafe45d91ae287f2e07b2e79709308178 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xd3741ac9b3f280b0819191e4b30be4ecd990771e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x09579452bc3872727a5d105f342645792bb8a82b - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x8a24d7260cd02d3dfd8eefb66bc17ad4b17d494c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xd88611a629265c9af294ffdd2e7fa4546612273e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x9a86980d3625b4a6e69d8a4606d51cbc019e2002 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x1c7a460413dd4e964f96d8dfc56e7223ce88cd85 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x776aaef8d8760129a0398cf8674ee28cefc0eab9 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x28e29ec91db66733a94ee8e3b86a6199117baf99 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xb9898511bd2bad8bfc23eba641ef97a08f27e730 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x76baa16ff15d61d32e6b3576c3a8c83a25c2f180 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x2816a491dd0b7a88d84cbded842a618e59016888 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0xa7ea9d5d4d4c7cf7dbde5871e6d108603c6942a5 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/base/0x586e10db93630a4d2da6c6a34ba715305b556f04 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xf486ad071f3bee968384d2e39e2d8af0fcf6fd46 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x76d36d44dc4595e8d2eb3ad745f175eda134284f - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x1fa4a73a3f0133f0025378af00236f3abdee5d63 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xb3ed0a426155b79b898849803e3b36552f7ed507 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x0ef4a107b48163ab4b57fca36e1352151a587be4 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x62694d43ccb9b64e76e38385d15e325c7712a735 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xa2b726b1145a4773f68593cf171187d8ebe4d495 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xf275e1ac303a4c9d987a2c48b8e555a77fec3f1c - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x11a31b833d43853f8869c9eec17f60e3b4d2a753 - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z 0.8 https://app.uniswap.org/explore/tokens/celo/0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e - 2024-05-20T17:20:52.753Z + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbadff0ef41d2a68f22de21eabca8a59aaf495cf0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1fdd61ef9a5c31b9a2abc7d39c139c779e8412af + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4ade2b180f65ed752b6f1296d0418ad21eb578c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0c5cb676e38d6973837b9496f6524835208145a2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb69753c06bb5c366be51e73bfc0cc2e3dc07e371 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8143182a775c54578c8b7b3ef77982498866945d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x76e222b07c53d28b89b0bac18602810fc22b49a8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x18aaa7115705e8be94bffebde57af9bfc265b998 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7d8146cf21e8d7cbe46054e01588207b51198729 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfe0c30065b384f05761f15d0cc899d4f9f9cc0eb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1ce270557c1f68cfb577b856766310bf8b47fd9c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x793a5d8b30aab326f83d20a9370c827fea8fdc51 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xff836a5821e69066c87e268bc51b849fab94240c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf4d2888d29d722226fafa5d9b24f9164c092421e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8ed97a637a790be1feff5e888d43629dc05408f6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x31c8eacbffdd875c74b94b077895bd78cf1e64a3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc55126051b22ebb829d00368f4b12bde432de5da + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8881562783028f5c1bcb985d2283d5e170d88888 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x67466be17df832165f8c80a5a120ccc652bd7e69 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd939212f16560447ed82ce46ca40a63db62419b5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x88417754ff7062c10f4e3a4ab7e9f9d9cbda6023 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5afe3855358e112b5647b952709e6165e1c1eeee + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x02e7f808990638e9e67e1f00313037ede2362361 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd2bdaaf2b9cc6981fd273dcb7c04023bfbe0a7fe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x112b08621e27e10773ec95d250604a041f36c582 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x32b053f2cba79f80ada5078cb6b305da92bde6e1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5ac34c53a04b9aaa0bf047e7291fb4e8a48f2a18 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x26ebb8213fb8d66156f1af8908d43f7e3e367c1d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xe3b9cfb8ea8a4f1279fbc28d3e15b4d2d86f18a0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8207c1ffc5b6804f6024322ccf34f29c3541ae26 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x255f1b39172f65dc6406b8bee8b08155c45fe1b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x092baadb7def4c3981454dd9c0a0d7ff07bcfc86 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x53bcf6698c911b2a7409a740eacddb901fc2a2c6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x2ac2b254bc18cd4999f64773a966e4f4869c34ee + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x17fc002b466eec40dae837fc4be5c67993ddbd6f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xc8a4eea31e9b6b61c406df013dd4fec76f21e279 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xe4dddfe67e7164b0fe14e218d80dc4c08edc01cb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x7c8a1a80fdd00c9cccd6ebd573e9ecb49bfa2a59 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x1debd73e752beaf79865fd6446b0c970eae7732f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xaf5db6e1cc585ca312e8c8f7c499033590cf5c98 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x65559aa14915a70190438ef90104769e5e890a00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x7fb688ccf682d58f86d7e38e03f9d22e7705448b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x73cb180bf0521828d8849bc8cf2b920918e23032 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x2e3d870790dc77a83dd1d18184acc7439a53f475 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0xa00e3a3511aac35ca78530c85007afcd31753819 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x528cdc92eab044e1e39fe43b9514bfdab4412b98 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x1c954e8fe737f99f68fa1ccda3e51ebdb291948c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xf50d05a1402d0adafa880d36050736f9f6ee7dee + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xab0b2ddb9c7e440fac8e140a89c0dbcbf2d7bbff + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x8bc3ec2e7973e64be582a90b08cadd13457160fe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x64060ab139feaae7f06ca4e63189d86adeb51691 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x5ec03c1f7fa7ff05ec476d19e34a22eddb48acdc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x9627a3d6872be48410fcece9b1ddd344bf08c53e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x1ed02954d60ba14e26c230eec40cbac55fa3aeea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8d3419b9a18651f3926a205ee0b1acea1e7192de + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb56d0839998fd79efcd15c27cf966250aa58d6d3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x81f91fe59ee415735d59bd5be5cca91a0ea4fa69 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x87c211144b1d9bdaa5a791b8099ea4123dc31d21 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf4210f93bc68d63df3286c73eba08c6414f40c0d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xece7b98bd817ee5b1f2f536daf34d0b6af8bb542 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4c96a67b0577358894407af7bc3158fc1dffbeb5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x70737489dfdf1a29b7584d40500d3561bd4fe196 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x39353a32eceafe4979a8606512c046c3b6398cc4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x92fb1b7d9730b2f1bd4e2e91368c1eb6fdd2a009 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x174e33ef2effa0a4893d97dda5db4044cc7993a3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xfdc944fb59201fb163596ee5e209ebc8fa4dcdc5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x388e543a5a491e7b42e3fbcd127dd6812ea02d0d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x56a38e7216304108e841579041249feb236c887b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1804e3db872eed4141e482ff74c56862f2791103 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9de16c805a3227b9b92e39a446f9d56cf59fe640 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb8d98a102b0079b69ffbc760c8d857a31653e56e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x5d6812722c3693078e4a0dbe3e9affc27a0b2768 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x255f1b39172f65dc6406b8bee8b08155c45fe1b6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc2fe011c3885277c7f0e7ffd45ff90cadc8ecd12 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc1ffaef4e7d553bbaf13926e258a1a555a363a07 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4e73420dcc85702ea134d91a262c8ffc0a72aa70 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xecaf81eb42cd30014eb44130b89bcd6d4ad98b92 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x4eae52907dba9c370e9ee99f0ce810602a4f2c63 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x25d887ce7a35172c62febfd67a1856f20faebb00 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x382ea807a61a418479318efd96f1efbc5c1f2c21 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6468e79a80c0eab0f9a2b574c8d5bc374af59414 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3106a0a076bedae847652f42ef07fd58589e001f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd015422879a1308ba557510345e944b912b9ab73 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5de8ab7e27f6e7a1fff3e5b337584aa43961beef + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcf078da6e85389de507ceede0e3d217e457b9d49 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1bbf25e71ec48b84d773809b4ba55b6f4be946fb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7039cd6d7966672f194e8139074c3d5c4e6dcf65 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x943af17c37207c9d7a27d12cb5055542a0b7afa8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6d68015171eaa7af9a5a0a103664cf1e506ff699 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6942806d1b2d5886d95ce2f04314ece8eb825833 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x949d48eca67b17269629c7194f4b727d4ef9e5d6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9361adf2b72f413d96f81ff40d794b47ce13b331 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3bb1be077f3f96722ae92ec985ab37fd0a0c4c51 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xdbb7a34bf10169d6d2d0d02a6cbb436cf4381bfa + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x66bff695f3b16a824869a8018a3a6e3685241269 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x85d19fb57ca7da715695fcf347ca2169144523a7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x069d89974f4edabde69450f9cf5cf7d8cbd2568d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0fe13ffe64b28a172c58505e24c0c111d149bd47 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x111111111117dc0aa78b770fa6a738034120c302 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xdc7ac5d5d4a9c3b5d8f3183058a92776dc12f4f3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x482702745260ffd69fc19943f70cffe2cacd70e9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc555d625828c4527d477e595ff1dd5801b4a600e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9eec1a4814323a7396c938bc86aec46b97f1bd82 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x87d73e916d7057945c9bcd8cdd94e42a6f47f776 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x067def80d66fb69c276e53b641f37ff7525162f6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xdd157bd06c1840fa886da18a138c983a7d74c1d7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xe80772eaf6e2e18b651f160bc9158b2a5cafca65 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xb6093b61544572ab42a0e43af08abafd41bf25a6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x35ca1e5a9b1c09fa542fa18d1ba4d61c8edff852 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x83e60b9f7f4db5cdb0877659b1740e73c662c55b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x4d01397994aa636bdcc65c9e8024bc497498c3bb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xc3abc47863524ced8daf3ef98d74dd881e131c38 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x4d15a3a2286d883af0aa1b3f21367843fac63e07 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xfb7f8a2c0526d01bfb00192781b7a7761841b16c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x3809dcdd5dde24b37abe64a5a339784c3323c44f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x85955046df4668e1dd369d2de9f3aeb98dd2a369 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x554cd6bdd03214b10aafa3e0d4d42de0c5d2937b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x4318cb63a2b8edf2de971e2f17f77097e499459d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xab9cb20a28f97e189ca0b666b8087803ad636b3c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x6a8ec2d9bfbdd20a7f5a4e89d640f7e7ceba4499 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x385eeac5cb85a38a9a07a70c73e0a3271cfb54a7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x0169ec1f8f639b32eec6d923e24c2a2ff45b9dd6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xe161be4a74ab8fa8706a2d03e67c02318d0a0ad6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4d58608eff50b691a3b76189af2a7a123df1e9ba + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x420b0fa3de2efcf2b2fd04152eb1df36a09717cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1cd38856ee0fdfd65c757e530e3b1de3061008d3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xfad8cb754230dbfd249db0e8eccb5142dd675a0d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xda761a290e01c69325d12d82ac402e5a73d62e81 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xafb5d4d474693e68df500c9c682e6a2841f9661a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xfc5462143a3178cf044e97c491f6bcb5e38f173e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xed1978d01d4a8a9d6a43ac79403d5b8dfbed739b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xba71cb8ef2d59de7399745793657838829e0b147 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x10c1b6f768e13c624a4a23337f1a5ba5c9be0e4b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1b1514c76c54ce8807d7fdedf85c664eee734ece + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x58cd93c4a91c3940109fa27d700f5013b18b5dc2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xea6f7e7e0f46a9e0f4e2048eb129d879f609d632 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x30d19fb77c3ee5cfa97f73d72c6a1e509fa06aef + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xe2dca969624795985f2f083bcd0b674337ba130a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xbb7d61d2511fd2e63f02178ca9b663458af9fc63 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x59f4f336bf3d0c49dbfba4a74ebd2a6ace40539a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x62d0a8458ed7719fdaf978fe5929c6d342b0bfce + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb8fda5aee55120247f16225feff266dfdb381d4c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xca530408c3e552b020a2300debc7bd18820fb42f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3ffeea07a27fab7ad1df5297fa75e77a43cb5790 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcfeb09c3c5f0f78ad72166d55f9e6e9a60e96eec + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x467bccd9d29f223bce8043b84e8c8b282827790f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2077d81d0c5258230d5a195233941547cb5f0989 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa0bbbe391b0d0957f1d013381b643041d2ca4022 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd1b89856d82f978d049116eba8b7f9df2f342ff3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x62f03b52c377fea3eb71d451a95ad86c818755d1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3927fb89f34bbee63351a6340558eebf51a19fb8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xacd2c239012d17beb128b0944d49015104113650 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x86b69f38bea3e02f68ff88534bc61ec60e772b19 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6873c95307e13beb58fb8fcddf9a99667655c9e4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x18084fba666a33d37592fa2633fd49a74dd93a88 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6e79b51959cf968d87826592f46f819f92466615 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x80ee5c641a8ffc607545219a3856562f56427fe9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0414d8c87b271266a5864329fb4932bbe19c0c49 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf57e7e7c23978c3caec3c3548e3d615c346e79ff + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xb0ffa8000886e57f86dd5264b9582b2ad87b2b91 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x1c986661170c1834db49c3830130d4038eeeb866 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x9ed7e4b1bff939ad473da5e7a218c771d1569456 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x7f9a7db853ca816b9a138aee3380ef34c437dee0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x371c7ec6d8039ff7933a2aa28eb827ffe1f52f07 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xb1bc21f748ae2be95674876710bc6d78235480e0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xadf5dd3e51bf28ab4f07e684ecf5d00691818790 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x1eba7a6a72c894026cd654ac5cdcf83a46445b08 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x38022a157b95c52d43abcac9bd09f028a1079105 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xd2507e7b5794179380673870d88b22f94da6abe0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xc708d6f2153933daa50b2d0758955be0a93a8fec + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x0052074d3eb1429f39e5ea529b54a650c21f5aa4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x4e78011ce80ee02d2c3e649fb657e45898257815 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x7583feddbcefa813dc18259940f76a02710a8905 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xe78aee6ccb05471a69677fb74da80f5d251c042b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x04f177fcacf6fb4d2f95d41d7d3fee8e565ca1d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xa6da8c8999c094432c77e7d318951d34019af24b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6d3b8c76c5396642960243febf736c6be8b60562 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7cf7132ede0ca592a236b6198a681bb7b42dd5ae + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x3afeae00a594fbf2e4049f924e3c6ac93296b6e8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0a93a7be7e7e426fc046e204c44d6b03a302b631 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc9b6ef062fab19d3f1eabc36b1f2e852af1acd18 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1754e5aadce9567a95f545b146a616ce34eead53 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xdb173587d459ddb1b9b0f2d6d88febef039304a2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x10a7a84c91988138f8dbbc82a23b02c8639e2552 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x92af6f53febd6b4c6f5293840b6076a1b82c4bc2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xeb9e49fb4c33d9f6aefb1b03f9133435e24c0ec6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1b2c141479757b8643a519be4692904088d860b2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4d25e94291fe8dcfbfa572cbb2aaa7b755087c91 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8e0e798966382e53bfb145d474254cbe065c17dc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4b6f82a4ed0b9e3767f53309b87819a78d041a7f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x004aa1586011f3454f487eac8d0d5c647d646c69 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x741777f6b6d8145041f73a0bddd35ae81f55a40f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xc6c58f600917de512cd02d2b6ed595ab54b4c30f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x03aa6298f1370642642415edc0db8b957783e8d6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x3ee2200efb3400fabb9aacf31297cbdd1d435d47 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x0d8ce2a99bb6e3b7db580ed848240e4a0f9ae153 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xa697e272a73744b343528c3bc4702f2565b2f422 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x301af3eff0c904dc5ddd06faa808f653474f7fcc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x776f9987d9deed90eed791cbd824d971fd5ccf09 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xf7de7e8a6bd59ed41a4b5fe50278b3b7f31384df + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x19e6bfc1a6e4b042fb20531244d47e252445df01 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x4338665cbb7b2485a8855a139b75d5e34ab0db94 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x2940566eb50f15129238f4dc599adc4f742d7d8e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xbb73bb2505ac4643d5c0a99c2a1f34b3dfd09d11 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x4ea98c1999575aaadfb38237dd015c5e773f75a2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/celo/0x1d18d0386f51ab03e7e84e71bda1681eba865f1f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x57b96d4af698605563a4653d882635da59bf11af + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd33526068d116ce69f19a9ee46f0bd304f21a51f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2a5fa016ffb20c70e2ef36058c08547f344677aa + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbe0ed4138121ecfc5c0e56b40517da27e6c5226b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9fd9278f04f01c6a39a9d1c1cd79f7782c6ade08 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x054c9d4c6f4ea4e14391addd1812106c97d05690 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7613c48e0cd50e42dd9bf0f6c235063145f6f8dc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x614da3b37b6f66f7ce69b4bbbcf9a55ce6168707 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x069e4aa272d17d9625aa3b6f863c7ef6cfb96713 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x24da31e7bb182cb2cabfef1d88db19c2ae1f5572 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7d4a23832fad83258b32ce4fd3109ceef4332af4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb58e61c3098d85632df34eecfb899a1ed80921cb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x67c4d14861f9c975d004cfb3ac305bee673e996e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x69babe9811cc86dcfc3b8f9a14de6470dd18eda4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x32f0d04b48427a14fb3cbc73db869e691a9fec6f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4cff49d0a19ed6ff845a9122fa912abcfb1f68a6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x51cb253744189f11241becb29bedd3f1b5384fdb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcf4c91ecafc43c9f382db723ba20b82efa852821 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6968676661ac9851c38907bdfcc22d5dd77b564d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0d438f3b5175bebc262bf23753c1e53d03432bde + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb98d4c97425d9908e66e53a6fdf673acca0be986 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x68a47fe1cf42eba4a030a10cd4d6a1031ca3ca0a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8a370c951f34e295b2655b47bb0985dd08d8f718 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x525574c899a7c877a11865339e57376092168258 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd9a442856c234a39a81a089c06451ebaa4306a72 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x1c43d05be7e5b54d506e3ddb6f0305e8a66cd04e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xb766039cc6db368759c1e56b79affe831d0cc507 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x18c14c2d707b2212e17d1579789fc06010cfca23 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xe0ee18eacafddaeb38f8907c74347c44385578ab + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x56659245931cb6920e39c189d2a0e7dd0da2d57b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xb6a5ae40e79891e4deadad06c8a7ca47396df21c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x04565fe9aa3ae571ada8e1bebf8282c4e5247b2a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf8a99f2bf2ce5bb6ce4aafcf070d8723bc904aa2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x3b9728bd65ca2c11a817ce39a6e91808cceef6fd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6797b6244fa75f2e78cdffc3a4eb169332b730cc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xe2c86869216ac578bd62a4b8313770d9ee359a05 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x47b464edb8dc9bc67b5cd4c9310bb87b773845bd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x28a730de97dc62a8c88363e0b1049056f1274a70 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xba5ede8d98ab88cea9f0d69918dde28dc23c2553 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8319767a7b602f88e376368dca1b92d38869b9b4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x461ee40928677644b8195662ab91bcdaae6ef105 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x24569d33653c404f90af10a2b98d6e0030d3d267 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x22222bd682745cf032006394750739684e45a5f8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9124577428c5bd73ad7636cbc5014081384f29d6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xaa6cccdce193698d33deb9ffd4be74eaa74c4898 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xe095780ba2a64a4efa7a74830f0b71656f0b0ad4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb59c8912c83157a955f9d715e556257f432c35d7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7771450ece9c61430953d2646f995e33a06c91f5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc48823ec67720a04a9dfd8c7d109b2c3d6622094 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x9ec02756a559700d8d9e79ece56809f7bcc5dc27 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3593d125a4f7849a1b059e64f4517a86dd60c95d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb0ffa8000886e57f86dd5264b9582b2ad87b2b91 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6985884c4392d348587b19cb9eaaf157f13271cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa045fe936e26e1e1e1fb27c1f2ae3643acde0171 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbeef698bd78139829e540622d5863e723e8715f1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x426a688ee72811773eb64f5717a32981b56f10c1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x873259322be8e50d80a4b868d186cc5ab148543a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x661c70333aa1850ccdbae82776bb436a0fcfeefb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0a2c375553e6965b42c135bb8b15a8914b08de0c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6fba952443be1de22232c824eb8d976b426b3c38 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1abaea1f7c830bd89acc67ec4af516284b1bc33c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb60fdf036f2ad584f79525b5da76c5c531283a1b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5a3e6a77ba2f983ec0d371ea3b475f8bc0811ad5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x55296f69f40ea6d20e478533c15a6b08b654e758 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1a7e4e63778b4f12a199c062f3efdd288afcbce8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x45804880de22913dafe09f4980848ece6ecbaf78 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xe5018913f2fdf33971864804ddb5fca25c539032 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x6985884c4392d348587b19cb9eaaf157f13271cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x2c650dab03a59332e2e0c0c4a7f726913e5028c1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x9aee3c99934c88832399d6c6e08ad802112ebeab + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x439c0cf1038f8002a4cad489b427e217ba4b42ad + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x6985884c4392d348587b19cb9eaaf157f13271cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x6985884c4392d348587b19cb9eaaf157f13271cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6985884c4392d348587b19cb9eaaf157f13271cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb79dd08ea68a908a97220c76d19a6aa9cbde4376 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4b61e2f1bbdee6d746209a693156952936f1702c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7480527815ccae421400da01e052b120cc4255e9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7466de7bb8b5e41ee572f4167de6be782a7fa75d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x298d411511a05dc1b559ed8f79c56bee06687b14 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8e16d46cb2da01cdd49601ec73d7b0344969ae33 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x18dd5b087bca9920562aff7a0199b96b9230438b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x37f0c2915cecc7e977183b8543fc0864d03e064c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x37f24b26bcefbfac7f261b97f8036da98f81a299 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xacb5b33ce55ba7729e38b2b59677e71c0112f0d9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x6985884c4392d348587b19cb9eaaf157f13271cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xc71b5f631354be6853efe9c3ab6b9590f8302e81 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7e744bbb1a49a44dfcc795014a4ba618e418fbbe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0c04ff41b11065eed8c9eda4d461ba6611591395 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x636bd98fc13908e475f56d8a38a6e03616ec5563 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x590246bfbf89b113d8ac36faeea12b7589f7fe5b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x80034f803afb1c6864e3ca481ef1362c54d094b9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x73fbd93bfda83b111ddc092aa3a4ca77fd30d380 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xff33a6b3dc0127862eedd3978609404b22298a54 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc770eefad204b5180df6a14ee197d99d808ee52d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa0385e7283c83e2871e9af49eec0966088421ddd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb2617246d0c6c0087f18703d576831899ca94f01 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xba386a4ca26b85fd057ab1ef86e3dc7bdeb5ce70 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9ebb0895bd9c7c9dfab0d8d877c66ba613ac98ea + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd12a99dbc40036cec6f1b776dccd2d36f5953b94 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8ab2ff0116a279a99950c66a12298962d152b83c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x420698cfdeddea6bc78d59bc17798113ad278f9d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa8c8cfb141a3bb59fea1e2ea6b79b5ecbcd7b6ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd8e8438cf7beed13cfabc82f300fb6573962c9e3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb1c9d42fa4ba691efe21656a7e6953d999b990c4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xdadeca1167fe47499e53eb50f261103630974905 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xa05245ade25cc1063ee50cf7c083b4524c1c4302 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x4fafad147c8cd0e52f83830484d164e960bdc6c3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x4dd9077269dd08899f2a9e73507125962b5bc87f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8931ee05ec111325c1700b68e5ef7b887e00661d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x26f1bb40ea88b46ceb21557dc0ffac7b7c0ad40f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x642e993fa91ffe9fb24d39a8eb0e0663145f8e92 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0c41f1fc9022feb69af6dc666abfe73c9ffda7ce + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf7ccb8a6e3400eb8eb0c47619134f7516e025215 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x2416092f143378750bb29b79ed961ab195cceea5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf0268c5f9aa95baf5c25d646aabb900ac12f0800 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0c067fc190cde145b0c537765a78d4e19873a5cc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xbe5614875952b1683cb0a2c20e6509be46d353a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x87a0233a8cb4392ec3eb8fa467817fc0b6a326dd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xdfbea88c4842d30c26669602888d746d30f9d60d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x80b3455e1db60b4cba46aba12e8b1e256dd64979 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x747747e47a48c669be384e0dfb248eee6ba04039 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/celo/0x50e85c754929840b58614f48e29c64bc78c58345 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x02f92800f57bcd74066f5709f1daa1a4302df875 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x967da4048cd07ab37855c090aaf366e4ce1b9f48 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x729031b3995538ddf6b6bce6e68d5d6fdeb3ccb5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x97a9a15168c22b3c137e6381037e1499c8ad0978 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5faa989af96af85384b8a938c2ede4a7378d9875 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4691937a7508860f876c9c0a2a617e7d9e945d4b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x037a54aab062628c9bbae1fdb1583c195585fe41 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xcb8b5cd20bdcaea9a010ac1f8d835824f5c87a04 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xdfb8be6f8c87f74295a87de951974362cedcfa30 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x354a6da3fcde098f8389cad84b0182725c6c91de + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x3f56e0c36d275367b8c502090edf38289b3dea0d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x6f9590958ce2beaf9c92a3a8fca6d1ddf310e052 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x3e5d9d8a63cc8a88748f229999cf59487e90721e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0xecc68d0451e20292406967fe7c04280e5238ac7d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xf1c1a3c2481a3a8a3f173a9ab5ade275292a6fa3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xb5e0cfe1b4db501ac003b740665bf43192cc7853 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xffa188493c15dfaf2c206c97d8633377847b6a52 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xb5c064f955d8e7f38fe0460c556a72987494ee17 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xf0949dd87d2531d665010d6274f06a357669457a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x14e5386f47466a463f85d151653e1736c0c50fc3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xadac33f543267c4d59a8c299cf804c303bc3e4ac + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xcfa3ef56d303ae4faaba0592388f19d7c3399fb4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x67ce18961c3269ca03c2e5632f1938cc53e614a1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x48164ea5df090e80a0eaee1147e466ea28669221 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x3054e8f8fba3055a42e5f5228a2a4e2ab1326933 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x42069d11a2cc72388a2e06210921e839cfbd3280 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x74ff3cbf86f95fea386f79633d7bc4460d415f34 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x2d6a3893966dda77749cc7e4003ab15f5cfa3cc1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x51b75da3da2e413ea1b8ed3eb078dc712304761c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8ad5b9007556749de59e088c88801a3aaa87134b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xbd97693278f1948c59f65f130fd87e7ff7c61d11 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x3992b27da26848c2b19cea6fd25ad5568b68ab98 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x34980c35353a8d7b1a1ba02e02e387a8383e004a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xdebd6e2da378784a69dc6ec99fe254223b312287 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/celo/0x456a3d042c0dbd3db53d5489e98dfb038553b0d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/celo/0x9995cc8f20db5896943afc8ee0ba463259c931ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x30d20208d987713f46dfd34ef128bb16c404d10f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x19848077f45356b21164c412eff3d3e4ff6ebc31 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x53206bf5b6b8872c1bb0b3c533e06fde2f7e22e4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x07ddacf367f0d40bd68b4b80b4709a37bdc9f847 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbdbe9f26918918bd3f43a0219d54e5fda9ce1bb3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb9d09bc374577dac1ab853de412a903408204ea8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xe72b141df173b999ae7c1adcbf60cc9833ce56a8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x214549b0317564de15770561221433fb3e8c995c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc82e3db60a52cf7529253b4ec688f631aad9e7c2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf3dcbc6d72a4e1892f7917b7c43b74131df8480e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x62e3b3c557c792c4a70765b3cdb5b56b1879f82d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2598c30330d5771ae9f983979209486ae26de875 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd4f4d0a10bcae123bb6655e8fe93a30d01eebd04 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xa0995d43901551601060447f9abf93ebc277cec2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x40379a439d4f6795b6fc9aa5687db461677a2dba + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x433cde5a82b5e0658da3543b47a375dffd126eb6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x619c4bbbd65f836b78b36cbe781513861d57f39d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1e0bb24ed6c806c01ef2f880a4b91adb90099ea7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0dd7913197bfb6d2b1f03f9772ced06298f1a644 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xfbb75a59193a3525a8825bebe7d4b56899e2f7e1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc3de830ea07524a0761646a6a4e4be0e114a3c83 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x3792dbdd07e87413247df995e692806aa13d3299 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x527856315a4bcd2f428ea7fa05ea251f7e96a50a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x292fcdd1b104de5a00250febba9bc6a5092a0076 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd749b369d361396286f8cc28a99dd3425ac05619 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfe3e6a25e6b192a42a44ecddcd13796471735acf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa1faa113cbe53436df28ff0aee54275c13b40975 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8802269d1283cdb2a5a329649e5cb4cdcee91ab6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0000bdaa645097ef80f9d475f341d0d107a45b3a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x683a4ac99e65200921f556a19dadf4b0214b5938 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x36c7188d64c44301272db3293899507eabb8ed43 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8a2279d4a90b6fe1c4b30fa660cc9f926797baa2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf418588522d5dd018b425e472991e52ebbeeeeee + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6135177a17e02658df99a07a2841464deb5b8589 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcf91b70017eabde82c9671e30e5502d312ea6eb2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x45080a6531d671ddff20db42f93792a489685e32 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x790814cd782983fab4d7b92cf155187a865d9f18 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9e6be44cc1236eef7e1f197418592d363bedcd5a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xaa7a9ca87d3694b5755f213b5d04094b8d0f0a6f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x69ee720c120ec7c9c52a625c04414459b3185f23 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x408e41876cccdc0f92210600ef50372656052a38 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5cf04716ba20127f1e2297addcf4b5035000c9eb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8290333cef9e6d528dd5618fb97a76f268f3edd4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1929761e87667283f087ea9ab8370c174681b4e9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x888888848b652b3e3a0f34c96e00eec0f3a23f72 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf944e35f95e819e752f3ccb5faf40957d311e8c5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1f70300bce8c2302780bd0a153ebb75b8ca7efcb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x3de81ce90f5a27c5e6a5adb04b54aba488a6d14e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xc87b37a581ec3257b734886d9d3a581f5a9d056c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x1a6b3a62391eccaaa992ade44cd4afe6bec8cff1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x65c936f008bc34fe819bce9fa5afd9dc2d49977f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x07d65c18cecba423298c0aeb5d2beded4dfd5736 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x51fc0f6660482ea73330e414efd7808811a57fa2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xcbe94d75ec713b7ead84f55620dc3174beeb1cfe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xd3144ff5f388d36c0a445686c08540296d8b209b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x433e39ce74aef8f409182541269e417ad9b56011 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xb1a03eda10342529bbf8eb700a06c60441fef25d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6b9bb36519538e0c073894e964e90172e1c0b41f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x689644b86075ed61c647596862c7403e1c474dbf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9a6d24c02ec35ad970287ee8296d4d6552a31dbe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x506beb7965fc7053059006c7ab4c62c02c2d989f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x31b28012f61fc3600e1c076bafc9fd997fb2da90 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xd7d919ea0c33a97ad6e7bd4f510498e2ec98cb78 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xef553b6914dbd17567393f7e55fbd773fff7d0cb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xe642657e4f43e6dcf0bd73ef24008394574dee28 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf8b1b47aa748f5c7b5d0e80c726a843913eb573a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xd064c53f043d5aee2ac9503b13ee012bf2def1d0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xfc60aa1ffca50ce08b3cdec9626c0bb9e9b09bec + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x82c8f48ac694841360de84d649a0d48d239b61f8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7d89e05c0b93b24b5cb23a073e60d008fed1acf9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7546e0d4d947a15f914e33de6616ffed826f45ef + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x9a5350edf28c1f93bb36d6e94b5c425fde8e222d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xaa076b62efc6f357882e07665157a271ab46a063 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6a6aa13393b7d1100c00a57c76c39e8b6c835041 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x07040971246a73ebda9cf29ea1306bb47c7c4e76 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6df0e641fc9847c0c6fde39be6253045440c14d3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2b640a99991dea2916205ecdc9f9c58f80017ed8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x38e4adb44ef08f22f5b5b76a8f0c2d0dcbe7dca1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x42069cc15f5befb510430d22ff1c9a1b3ae22cfe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x89fd2d8fd8d937f55c89b7da3ceed44fa27e4a81 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x76bc677d444f1e9d57daf5187ee2b7dc852745ae + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa0084063ea01d5f09e56ef3ff6232a9e18b0bacd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4abd5745f326932b1b673bfa592a20d7bb6bc455 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xe53ec727dbdeb9e2d5456c3be40cff031ab40a55 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf43f21384d03b5cbbddd58d2de64071e4ce76ab0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x33349b282065b0284d756f0577fb39c158f935e6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x33c88d4cac6ac34f77020915a2a88cd0417dc069 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xdce765f021410b3266aa0053c93cb4535f1e12e0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xb50a8e92cb9782c9b8f3c88e4ee8a1d0aa2221d7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x0a84edf70f30325151631ce7a61307d1f4d619a3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xc11158c5da9db1d553ed28f0c2ba1cbedd42cfcb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xdc4f4ed9872571d5ec8986a502a0d88f3a175f1e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9beec80e62aa257ced8b0edd8692f79ee8783777 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf95e1c0a67492720ca22842122fe7fa63d5519e5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xca8e8d244f0d219a6fc9e4793c635cea98d0399c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6a4f69da1e2fb2a9b11d1aad60d03163fe567732 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0718f45bbf4781ce891e4e18182f025725f0fc95 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x132bbda4a40d4d6288be49b637ec2c113b5d7600 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9aaae745cf2830fb8ddc6248b17436dc3a5e701c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x24fcfc492c1393274b6bcd568ac9e225bec93584 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x21fd16cd0ef24a49d28429921e335bb0c1bfadb3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa469b7ee9ee773642b3e93e842e5d9b5baa10067 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8c19f7854b27758ddffdcdc8908f22bf55e00736 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xf2ae0038696774d65e67892c9d301c5f2cbbda58 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6bc40d4099f9057b23af309c08d935b890d7adc0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xee2a03aa6dacf51c18679c516ad5283d8e7c2637 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7f911119435d8ded9f018194b4b6661331379a3d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x777be1c6075c20184c4fd76344b7b0b7c858fe6b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x812ba41e071c7b7fa4ebcfb62df5f45f6fa853ee + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x881d4c8618d68872fa404518b2460ea839a02a6a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xba2ae4e0a9c6ecaf172015aa2cdd70a21f5a290b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1caf237d7a2d103e3e9b1855988c01ac10344600 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7d4a7be025652995364e0e232063abd9e8d65e6e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x620aa20875ec1144126ea47fb27ecfe6e10d0c56 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfae103dc9cf190ed75350761e95403b7b8afa6c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xae7ab96520de3a18e5e111b5eaab095312d7fe84 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x04c154b66cb340f3ae24111cc767e0184ed00cc6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x70e8de73ce538da2beed35d14187f6959a8eca96 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfb7b4564402e5500db5bb6d63ae671302777c75a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6810e776880c02933d47db1b9fc05908e5386b96 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x11e969e9b3f89cb16d686a03cd8508c9fc0361af + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x8b5d1d8b3466ec21f8ee33ce63f319642c026142 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x3ed03e95dd894235090b3d4a49e0c3239edce59e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xb3f13b0c61d65d67d7d6215d70c89533ee567a91 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xfea31d704deb0975da8e77bf13e04239e70d7c28 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x66e535e8d2ebf13f49f3d49e5c50395a97c137b1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x9a06db14d639796b25a6cec6a1bf614fd98815ec + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7fdd7419428955dbf36d4176af5a8f09ad29d1f3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8c9037d1ef5c6d1f6816278c7aaf5491d24cd527 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xa9f5031b54c44c3603b4300fde9b8f5cd18ad06f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x57f5fbd3de65dfc0bd3630f732969e5fb97e6d37 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9ef1139e6b420cc929dd912a5a7adeced6f12e91 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x120edc8e391ba4c94cb98bb65d8856ae6ec1525f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xd7ea82d19f1f59ff1ae95f1945ee6e6d86a25b96 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x2c9ab600d71967ff259c491ad51f517886740cbc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xf4c8e32eadec4bfe97e0f595add0f4450a863a11 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x8c49a510756224e887b3d99d00d959f2d86dda1c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7777cec341e7434126864195adef9b05dcc3489c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x19af07b52e5faa0c2b1e11721c52aa23172fe2f5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb7109df1a93f8fe2b8162c6207c9b846c1c68090 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbbc2ae13b23d715c30720f079fcd9b4a74093505 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x595832f8fc6bf59c85c527fec3740a1b7a361269 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7316d973b0269863bbfed87302e11334e25ea565 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2be8e422cb4a5a7f217a8f1b0658952a79132f28 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x83e6f1e41cdd28eaceb20cb649155049fac3d5aa + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbabe3ce7835665464228df00b03246115c30730a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2e6a60492fb5b58f5b5d08c7cafc75e740e6dc8e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc08e7e23c235073c6807c2efe7021304cb7c2815 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x955d5c14c8d4944da1ea7836bd44d54a8ec35ba1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3540abe4f288b280a0740ad5121aec337c404d15 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfe8526a77a2c3590e5973ba81308b90bea21fbff + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd807f7e2818db8eda0d28b5be74866338eaedb86 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x4186bfc76e2e237523cbc30fd220fe055156b41f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xd5d3aa404d7562d09a848f96a8a8d5d65977bf90 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xa3f751662e282e83ec3cbc387d225ca56dd63d3a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xd24157aa1097486dc9d7cf094a7e15026e566b5d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xbed0b9240bdbcc8e33f66d2ca650a5ef60a5bab0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x5d559ea7bb2dae4b694a079cb8328a2145fd32f6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x97b959385dfdcaf252223838746beb232ac601aa + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x18e692c03de43972fe81058f322fa542ae1a5e2c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x38029c62dfa30d9fd3cadf4c64e9b2ab21dbda17 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x4507cef57c46789ef8d1a19ea45f4216bae2b528 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/celo/0x73f93dcc49cb8a239e2032663e9475dd5ef29a08 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x9e523234d36973f9e38642886197d023c88e307e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5de758bba013e58dae2693aea3f0b12b31a3023d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1001271083c249bd771e1bb76c22d935809a61ee + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9d39a5de30e57443bff2a8307a4256c8797a3497 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf3768d6e78e65fc64b8f12ffc824452130bd5394 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf2ec4a773ef90c58d98ea734c0ebdb538519b988 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0f2d719407fdbeff09d87557abb7232601fd9f29 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x180000dda70eb7fb7f3e10e52e88ce88f46e3b3a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xed89fc0f41d8be2c98b13b7e3cd3e876d73f1d30 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x17c50d62e6e8d20d2dc18e9ad79c43263d0720d9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3b50805453023a91a8bf641e279401a0b23fa6f9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfd03723a9a3abe0562451496a9a394d2c4bad4ab + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfe67a4450907459c3e1fff623aa927dd4e28c67a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc5fb36dd2fb59d3b98deff88425a3f425ee469ed + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x6b021b3f68491974be6d4009fee61a4e3c708fd6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x7ae9ab13fc8945323b778b3f8678145e80ec2efb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xbc4c97fb9befaa8b41448e1dfcc5236da543217f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x93919784c523f39cacaa98ee0a9d96c3f32b593e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xd55fce7cdab84d84f2ef3f99816d765a2a94a509 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x32e0f9d26d1e33625742a52620cc76c1130efde6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9b700b043e9587dde9a0c29a9483e2f8fa450d54 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0b1594b0e896bf165d925956e0df733b8443af6a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x891502ba08132653151f822a3a430198f1844115 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc702b80a1bebac118cab22ce6f2978ef59563b3f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1287a235474e0331c0975e373bdd066444d1bd35 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xab36452dbac151be02b16ca17d8919826072f64a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xcc7ff230365bd730ee4b352cc2492cedac49383e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xa9b038285f43cd6fe9e16b4c80b4b9bccd3c161b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x77be1ba1cd2d7a63bffc772d361168cc327dd8bc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x00000000efe302beaa2b3e6e1b18d08d69a9012a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd101dcc414f310268c37eeb4cd376ccfa507f571 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd09eb9099fac55edcbf4965e0a866779ca365a0c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7b0df1cd724ec34ec9bc4bd19749b01afb490761 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x71297312753ea7a2570a5a3278ed70d9a75f4f44 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x9e32b13ce7f2e80a01932b42553652e053d6ed8e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6942040b6d25d6207e98f8e26c6101755d67ac89 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x3301ee63fb29f863f2333bd4466acb46cd8323e6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfefe157c9d0ae025213092ff9a5cb56ab492bab8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x44108f0223a3c3028f5fe7aec7f9bb2e66bef82f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1121acc14c63f3c872bfca497d10926a6098aac5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf1376bcef0f78459c0ed0ba5ddce976f1ddf51f4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xce722f60f35c37ab295adc4e6ba45bcc7ca89dd6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x614577036f0a024dbc1c88ba616b394dd65d105a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x93fa0b88c0c78e45980fa74cdd87469311b7b3e4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0xe22c452bd2ade15dfc8ad98286bc6bdf0c9219b7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x00000000000451f49c692bfc24971cacea2db678 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x00000000702749f73e5210b08b0a3d440078f888 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x86f65121804d2cdbef79f9f072d4e0c2eebabc08 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x127e47aba094a9a87d084a3a93732909ff031419 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x52b492a33e447cdb854c7fc19f1e57e8bfa1777d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x55027a5b06f4340cc4c82dcc74c90ca93dcb173e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x32b133add6d99d085ff23f522662b546b70d54a1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x2ad3d80c917ddbf08acc04277f379e00e4d75395 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc73dc7ae7a4fa40517aafa941ae1ee436b91a12c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x9f235d23354857efe6c541db92a9ef1877689bcb + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0c90c756350fb803a7d5d9f9ee5ac29e77369973 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xac12f930318be4f9d37f602cbf89cd33e99aa9d4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x1c45366641014069114c78962bdc371f534bc81c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc328a59e7321747aebbc49fd28d1b32c1af8d3b2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x90edf25b14393350f0c1b5b12b6cb3cd3781fb4a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x590f820444fa3638e022776752c5eef34e2f89a6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1fdb29ad49330b07ae5a87483f598aa6b292039e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4a220e6096b25eadb88358cb44068a3248254675 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xabd4c63d2616a5201454168269031355f4764337 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4c1b1302220d7de5c22b495e78b72f2dd2457d45 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x050c24dbf1eec17babe5fc585f06116a259cc77a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x57211299bc356319ba5ca36873eb06896173f8bc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xfde4c96c8593536e31f229ea8f37b2ada2699bb2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf9b738c2e7adc4f299c57afd0890b925a5efea6f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x04c0599ae5a44757c0af6f9ec3b93da8976c150a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x99b2b1a2adb02b38222adcd057783d7e5d1fcc7d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf9569cfb8fd265e91aa478d86ae8c78b8af55df4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xa3d1a8deb97b111454b294e2324efad13a9d8396 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xd85eff20288ca72ea9eecffb428f89ee5066ca5c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x13f4196cc779275888440b3000ae533bbbbc3166 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x160452f95612699d1a561a70eeeeede67c6812af + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x5ce12f6d9f2fcaf0b11494a1c39e09eeb16ca7e8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x6894cde390a3f51155ea41ed24a33a4827d3063d + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6db6fdb5182053eecec778afec95e0814172a474 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc0cfbe1602dd586349f60e4681bf4badca584ec9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x289ff00235d2b98b0145ff5d4435d3e92f9540a6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcb76314c2540199f4b844d4ebbc7998c604880ca + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd7cfdb3cdc33dbeb9e9a4c95b61953cf12a008b3 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xce176825afc335d9759cb4e323ee8b31891de747 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8f2bf2f59cdf7be4aee71500b9419623202b8636 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x744d70fdbe2ba4cf95131626614a1763df805b9e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x52e6654aee5d59e13ae30b48f8f5dbeb97f708cd + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x38f9bf9dce51833ec7f03c9dc218197999999999 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x7189fb5b6504bbff6a852b13b7b82a3c118fdc27 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x38f9bf9dce51833ec7f03c9dc218197999999999 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x8349314651ede274f8c5fef01aa65ff8da75e57c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x38f9bf9dce51833ec7f03c9dc218197999999999 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x1adcef5c780d8895ac77e6ee9239b4b3ecb76da2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x38f9bf9dce51833ec7f03c9dc218197999999999 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x917f39bb33b2483dd19546b1e8d2f09ce481ee44 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x8b67f2e56139ca052a7ec49cbcd1aa9c83f2752a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x029c58a909fbe3d4be85a24f414dda923a3fde0f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x655a51e6803faf50d4ace80fa501af2f29c856cf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x9ca5dfa3b0b187d7f53f4ef83ca435a2ec2e4070 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xb68a20b9e9b06fde873897e12ab3372ce48f1a8a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x0203d275d2a65030889af45ed91d472be3948b92 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xa00453052a36d43a99ac1ca145dfe4a952ca33b8 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8236a87084f8b84306f72007f36f2618a5634494 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbc5ca3c518c8a2930947661237b1b562e34f22b7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfd0205066521550d7d7ab19da8f72bb004b4c341 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x880226cbcce551eeafd18c9a9e883c85811b82fc + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xfc21540d6b89667d167d42086e1feb04da3e9b21 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x41d06390b935356b46ad6750bda30148ad2044a4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8149745670881d99700078ede5903a1a7bebe262 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcf01a5c02c9b9dd5bf73a5a56bcdbc9dca483d43 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xae0fe8474cf5b1b412b3e4327a1c535ea12b77b7 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc98d64da73a6616c42117b582e832812e7b8d57f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x70c0b83501a3989d4f8a8693581bb7010194abb5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x80122c6a83c8202ea365233363d3f4837d13e888 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x455e53cbb86018ac2b8092fdcd39d8444affc3f6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x58aea10748a00d1781d6651f9d78a414ea32ca46 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x406d59819bc2aef682f4ff2769085c98a264f97b + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xc4ce1d6f5d98d65ee25cf85e9f2e9dcfee6cb5d6 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x94025780a1ab58868d9b2dbbb775f44b32e8e6e5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0xf33687811f3ad0cd6b48dd4b39f9f977bd7165a2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xa88594d404727625a9437c3f886c7643872296ae + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7e72d6410803c40e73806f2a72e3eade5d075cc0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x31ea904a7eca45122890deb8da3473a2081bc9d1 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x48c6740bcf807d6c47c864faeea15ed4da3910ab + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xc5fecc3a29fb57b5024eec8a2239d4621e111cbe + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x184cff0e719826b966025f93e05d8c8b0a79b3f9 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0c2e08e459fc43ddd1e2718c122f566473f59665 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1a3a8cf347b2bf5890d3d6a1b981c4f4432c8661 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8baf5d75cae25c7df6d1e0d26c52d19ee848301a + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x28561b8a2360f463011c16b6cc0b0cbef8dbbcad + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0fd10b9899882a6f2fcb5c371e17e70fdee00c38 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7a58c0be72be218b41c608b7fe7c5bb630736c71 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xddaf27167929cd045a7d97d09a4fa1046ece3d89 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x375e104af98872e5b4fe951919e504a47db1757c + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x5408d3883ec28c2de205064ae9690142b035fed2 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1bb4afbf2ce0c9ec86e6414ad4ba4d9aab1c0de4 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x7391425ca7cee3ee03e09794b819291a572af83e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x38e382f74dfb84608f3c1f10187f6bef5951de93 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xbea269038eb75bdab47a9c04d0f5c572d94b93d5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xf41a7b7c79840775f70a085c1fc5a762bbc6b180 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x13654df31871b5d01e5fba8e6c21a5d0344820f5 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x4d840b741bc05fde325d4ec0b4cfcd0cea237e4e + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x49b1be61a8ca3f9a9f178d6550e41e00d9162159 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf5bc3439f53a45607ccad667abc7daf5a583633f + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x0a953dd9fc813fefaf6015b804c9dfa0624690c0 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x44ec807ce2f4a6f2737a92e985f318d035883e47 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xfb6115445bff7b52feb98650c87f44907e58f802 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x117a123ded97cd125837d9ac19592b77d806fa88 + 2024-09-20T21:06:11.923Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd9fcd98c322942075a5c3860693e9f4f03aae07b + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x240cd7b53d364a208ed41f8ced4965d11f571b7a + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb8d6196d71cdd7d90a053a7769a077772aaac464 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xcbde0453d4e7d748077c1b0ac2216c011dd2f406 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x786f112c9a6bc840cdc07cfd840105efd6ef2d4b + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x0bffdd787c83235f6f0afa0faed42061a4619b7a + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1c43cd666f22878ee902769fccda61f401814efb + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x1b54a6fa1360bd71a0f28f77a1d6fba215d498c3 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xb528edbef013aff855ac3c50b381f253af13b997 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x888888ae2c4a298efd66d162ffc53b3f2a869888 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x4cd27e18757baa3a4fe7b0ab7db083002637a6c5 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x240d6faf8c3b1a7394e371792a3bf9d28dd65515 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x41b1f9dcd5923c9542b6957b9b72169595acbc5c + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xd1f2586790a5bd6da1e443441df53af6ec213d83 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8de5b80a0c1b02fe4976851d030b36122dbb8624 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x391cf4b21f557c935c7f670218ef42c21bd8d686 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8bd35250918ed056304fa8641e083be2c42308bb + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0xc3960227e41c3f54e9b399ce216149dea5315c34 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x59062301fb510f4ea2417b67404cb16d31e604ba + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x75ec618a817eb0a4a7e44ac3dfc64c963daf921a + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x7e7a7c916c19a45769f6bdaf91087f93c6c12f78 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x21ccbc5e7f353ec43b2f5b1fb12c3e9d89d30dca + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0x87eee96d50fb761ad85b1c982d28a042169d61b1 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x3c720206bfacb2d16fa3ac0ed87d2048dbc401fc + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/polygon/0x8d60fb5886497851aac8c5195006ecf07647ba0d + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xcb327b99ff831bf8223cced12b1338ff3aa322ff + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xf544251d25f3d243a36b07e7e7962a678f952691 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xa7296cefae8477a81e23230ca5d3a3d6f49d3764 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x051fb509e4a775fabd257611eea1efaed8f91359 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xae2bddbcc932c2d2cf286bad0028c6f5074c77b5 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x1dd2d631c92b1acdfcdd51a0f7145a50130050c4 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0xd3c68968137317a57a9babeacc7707ec433548b4 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x7f6f6720a73c0f54f95ab343d7efeb1fa991f4f7 + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xf3527ef8de265eaa3716fb312c12847bfba66cef + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x8888888888f004100c0353d657be6300587a6ccd + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xe2a59d5e33c6540e18aaa46bf98917ac3158db0d + 2024-09-27T19:51:14.628Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0xfa2ad87e35fc8d3c9f57d73c4667a4651ce6ad2f + 2024-09-27T19:51:14.628Z 0.8 \ No newline at end of file diff --git a/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx b/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx index 7b9bd1accd7..9f941292461 100644 --- a/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx +++ b/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx @@ -17,7 +17,7 @@ import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' import { LoadingBubble } from 'components/Tokens/loading' import Column from 'components/deprecated/Column' import Row, { AutoRow } from 'components/deprecated/Row' -import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import { useDisconnect } from 'hooks/useDisconnect' import useENSName from 'hooks/useENSName' diff --git a/apps/web/src/components/AccountDrawer/DefaultMenu.tsx b/apps/web/src/components/AccountDrawer/DefaultMenu.tsx index ba3389d290a..7294b36719e 100644 --- a/apps/web/src/components/AccountDrawer/DefaultMenu.tsx +++ b/apps/web/src/components/AccountDrawer/DefaultMenu.tsx @@ -47,7 +47,7 @@ function DefaultMenu({ drawerOpen }: { drawerOpen: boolean }) { }, 250) return () => clearTimeout(timer) } - return + return undefined }, [drawerOpen, menu, closeSettings]) useEffect(() => { @@ -58,6 +58,7 @@ function DefaultMenu({ drawerOpen }: { drawerOpen: boolean }) { sendAnalyticsEvent(InterfaceEventNameLocal.PortfolioMenuOpened, { name: menu }) }, [menu]) + // eslint-disable-next-line consistent-return const SubMenu = useMemo(() => { switch (menu) { case MenuState.DEFAULT: diff --git a/apps/web/src/components/AccountDrawer/IconButton.tsx b/apps/web/src/components/AccountDrawer/IconButton.tsx index 766ce926491..df135ca3770 100644 --- a/apps/web/src/components/AccountDrawer/IconButton.tsx +++ b/apps/web/src/components/AccountDrawer/IconButton.tsx @@ -168,10 +168,10 @@ export const IconWithConfirmTextButton = ({ // keyboard action to cancel useEffect(() => { if (typeof window === 'undefined') { - return + return undefined } if (!showText || !frame) { - return + return undefined } const closeAndPrevent = (e: Event) => { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx index 55364fe4d9d..6aad380de6d 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx @@ -19,7 +19,6 @@ import Column, { AutoColumn } from 'components/deprecated/Column' import Row from 'components/deprecated/Row' import { LimitDisclaimer } from 'components/swap/LimitDisclaimer' import { SwapModalHeaderAmount } from 'components/swap/SwapModalHeaderAmount' -import { Field } from 'components/swap/constants' import { useCurrency } from 'hooks/Tokens' import { useUSDPrice } from 'hooks/useUSDPrice' import { atom } from 'jotai' @@ -35,6 +34,7 @@ import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constant import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { Trans } from 'uniswap/src/i18n' import { UniverseChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { logger } from 'utilities/src/logger/logger' @@ -238,7 +238,7 @@ export function OrderContent({
252.074 @@ -442,7 +442,7 @@ exports[`OrderContent should render without error, filled order 1`] = ` >
0.10684 @@ -878,7 +878,7 @@ exports[`OrderContent should render without error, limit order 1`] = ` } .c4 img { - width: 20px; + width: 19px; height: 40px; object-fit: cover; } @@ -1019,7 +1019,7 @@ exports[`OrderContent should render without error, limit order 1`] = ` >
252.074 @@ -1089,7 +1089,7 @@ exports[`OrderContent should render without error, limit order 1`] = ` >
0.10684 @@ -1534,7 +1534,7 @@ exports[`OrderContent should render without error, open order 1`] = ` } .c4 img { - width: 20px; + width: 19px; height: 40px; object-fit: cover; } @@ -1664,7 +1664,7 @@ exports[`OrderContent should render without error, open order 1`] = ` >
252.074 @@ -1734,7 +1734,7 @@ exports[`OrderContent should render without error, open order 1`] = ` >
0.10684 diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts index 4d6c7387ab1..531962bd233 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts @@ -317,6 +317,7 @@ export async function signatureToActivity( switch (signature.type) { case SignatureType.SIGN_UNISWAPX_ORDER: case SignatureType.SIGN_UNISWAPX_V2_ORDER: + case SignatureType.SIGN_PRIORITY_ORDER: case SignatureType.SIGN_LIMIT: { // Only returns Activity items for orders that don't have an on-chain counterpart if (isOnChainOrder(signature.status)) { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx index 41bc895888f..16e3443e527 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx @@ -495,7 +495,7 @@ function parseUniswapXOrder(activity: OrderActivity): Activity | undefined { // If the order is open, do not render it. if (signature.status === UniswapXOrderStatus.OPEN) { - return + return undefined } const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity } = activity.details diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts index 85073f7bf82..a953f6bd2c0 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts @@ -1,7 +1,7 @@ import { TransactionRequest } from '@ethersproject/abstract-provider' import { Web3Provider } from '@ethersproject/providers' import { permit2Address } from '@uniswap/permit2-sdk' -import { CosignedV2DutchOrder, DutchOrder, getCancelMultipleParams } from '@uniswap/uniswapx-sdk' +import { CosignedPriorityOrder, CosignedV2DutchOrder, DutchOrder, getCancelMultipleParams } from '@uniswap/uniswapx-sdk' import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types' import { getYear, isSameDay, isSameMonth, isSameWeek, isSameYear } from 'date-fns' import { ContractTransaction } from 'ethers/lib/ethers' @@ -100,7 +100,9 @@ function getCancelMultipleUniswapXOrdersParams( .map(({ encodedOrder, type }) => type === SignatureType.SIGN_UNISWAPX_V2_ORDER ? CosignedV2DutchOrder.parse(encodedOrder, chainId) - : DutchOrder.parse(encodedOrder, chainId), + : type === SignatureType.SIGN_PRIORITY_ORDER + ? CosignedPriorityOrder.parse(encodedOrder, chainId) + : DutchOrder.parse(encodedOrder, chainId), ) .map((order) => order.info.nonce) return getCancelMultipleParams(nonces) @@ -153,7 +155,7 @@ async function cancelMultipleUniswapXOrders({ }) { const cancelParams = getCancelMultipleUniswapXOrdersParams(orders, chainId) if (!permit2 || !provider) { - return + return undefined } try { const transactions: ContractTransaction[] = [] @@ -177,7 +179,7 @@ async function getCancelMultipleUniswapXOrdersTransaction( ): Promise { const cancelParams = getCancelMultipleUniswapXOrdersParams(orders, chainId) if (!permit2 || cancelParams.length === 0) { - return + return undefined } try { const tx = await permit2.populateTransaction.invalidateUnorderedNonces(cancelParams[0].word, cancelParams[0].mask) @@ -211,7 +213,7 @@ export function useCreateCancelTransactionRequest( params.orders.filter(({ encodedOrder }) => Boolean(encodedOrder)).length === 0 || !permit2 ) { - return + return undefined } return getCancelMultipleUniswapXOrdersTransaction(params.orders, params.chainId, permit2) }, [params, permit2]) diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx index a3e35f8aa86..3ca93e7d6da 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx @@ -212,7 +212,7 @@ export default function useMultiChainPositions( // Fetches positions when existing positions are stale and the document has focus useEffect(() => { if (positionsFetching.current || cachedPositions?.stale === false) { - return + return undefined } else if (document.hasFocus()) { fetchAllPositions() } else { @@ -226,7 +226,7 @@ export default function useMultiChainPositions( window.removeEventListener('focus', onFocus) } } - return + return undefined }, [fetchAllPositions, positionsFetching, cachedPositions?.stale]) const positionsWithFeesAndPrices: PositionInfo[] | undefined = useMemo( diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx index f02737c0958..24bd3116b7b 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx @@ -8,7 +8,7 @@ import PortfolioRow, { import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' import Row from 'components/deprecated/Row' -import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { PortfolioBalance, PortfolioToken } from 'graphql/data/portfolios' import { getTokenDetailsURL, gqlToCurrency } from 'graphql/data/util' import styled from 'lib/styled-components' @@ -106,13 +106,13 @@ function TokenRow({ element={InterfaceElementName.MINI_PORTFOLIO_TOKEN_ROW} properties={{ chain_id: currency.chainId, - token_name: token?.project?.name ?? token?.name, + token_name: token?.name ?? token?.project?.name, address: token?.address, }} > } - title={{token?.project?.name ?? token?.name}} + title={{token?.name ?? token?.project?.name}} descriptor={ {formatNumber({ diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap b/apps/web/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap index 9e514fdd053..b1b6c9715ba 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap @@ -13,7 +13,7 @@ exports[`PortfolioLogo renders with L2 icon 1`] = ` } .c1 img { - width: 20px; + width: 19px; height: 40px; object-fit: cover; } @@ -136,7 +136,7 @@ exports[`PortfolioLogo renders without L2 icon 1`] = ` } .c1 img { - width: 20px; + width: 19px; height: 40px; object-fit: cover; } diff --git a/apps/web/src/components/AccountDrawer/index.tsx b/apps/web/src/components/AccountDrawer/index.tsx index c2640b5406b..a1aeb5a9adf 100644 --- a/apps/web/src/components/AccountDrawer/index.tsx +++ b/apps/web/src/components/AccountDrawer/index.tsx @@ -13,10 +13,11 @@ import styled, { css } from 'lib/styled-components' import { useEffect, useRef, useState } from 'react' import { ChevronsRight } from 'react-feather' import { useGesture } from 'react-use-gesture' -import { BREAKPOINTS, NAV_HEIGHT } from 'theme' +import { BREAKPOINTS } from 'theme' import { ClickableStyle } from 'theme/components' import { Z_INDEX } from 'theme/zIndex' import Trace from 'uniswap/src/features/telemetry/Trace' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' import { isMobileWeb } from 'utilities/src/platform' const DRAWER_WIDTH_XL = '390px' @@ -99,9 +100,9 @@ const Container = styled.div<{ isUniExtensionAvailable?: boolean; $open?: boolea const ExtensionContainerStyles = css` height: auto; - max-height: calc(100% - ${NAV_HEIGHT + 16}px); + max-height: calc(100% - ${INTERFACE_NAV_HEIGHT + 16}px); right: 12px; - top: ${NAV_HEIGHT}px; + top: ${INTERFACE_NAV_HEIGHT}px; ${ScrollBarStyles} ` @@ -115,8 +116,8 @@ const AccountDrawerWrapper = styled.div<{ open: boolean; isUniExtensionAvailable z-index: ${Z_INDEX.modal}; position: absolute; margin-right: 0; - top: ${({ open }) => (open ? `calc(-1 * (100% - ${NAV_HEIGHT}px))` : 0)}; - height: calc(100% - ${NAV_HEIGHT}px); + top: ${({ open }) => (open ? `calc(-1 * (100% - ${INTERFACE_NAV_HEIGHT}px))` : 0)}; + height: calc(100% - ${INTERFACE_NAV_HEIGHT}px); width: 100%; max-width: 100%; diff --git a/apps/web/src/components/AddressQRModal.tsx b/apps/web/src/components/AddressQRModal.tsx index 513011f5ed0..9a9be9ee7fd 100644 --- a/apps/web/src/components/AddressQRModal.tsx +++ b/apps/web/src/components/AddressQRModal.tsx @@ -58,9 +58,11 @@ export function AddressQRModal({ accountAddress }: { accountAddress: Address }) { } } +// eslint-disable-next-line consistent-return export function formatHistoryDuration(duration: HistoryDuration): string { switch (duration) { case HistoryDuration.FiveMinute: diff --git a/apps/web/src/components/Charts/VolumeChart/utils.ts b/apps/web/src/components/Charts/VolumeChart/utils.ts index b247b2666e6..b09ee580dae 100644 --- a/apps/web/src/components/Charts/VolumeChart/utils.ts +++ b/apps/web/src/components/Charts/VolumeChart/utils.ts @@ -205,7 +205,7 @@ export function calculateColumnPositionsInPlace( if (common.spacing > 0 && minColumnWidth < alignToMinimalWidthLimit) { ;(items as ColumnPositionItem[]).forEach((item: ColumnPositionItem, index: number) => { if (!item.column || index < startIndex || index > endIndex) { - return + return undefined } const width = item.column.right - item.column.left + 1 if (width <= minColumnWidth) { diff --git a/apps/web/src/components/Charts/utils.tsx b/apps/web/src/components/Charts/utils.tsx index e52e0b7a773..96d07f82b79 100644 --- a/apps/web/src/components/Charts/utils.tsx +++ b/apps/web/src/components/Charts/utils.tsx @@ -27,6 +27,7 @@ export const CHART_TYPE_LABELS: Record * Custom time formatter used to customize tick mark labels on the time scale. * Follows the function signature of lightweight-charts' TickMarkFormatter. */ +// eslint-disable-next-line consistent-return export function formatTickMarks(time: UTCTimestamp, tickMarkType: TickMarkType, locale: string): string { const date = new Date(time.valueOf() * 1000) switch (tickMarkType) { diff --git a/apps/web/src/components/ConfirmSwapModal/Pending.tsx b/apps/web/src/components/ConfirmSwapModal/Pending.tsx index 0f8bf41bb75..596117e17c8 100644 --- a/apps/web/src/components/ConfirmSwapModal/Pending.tsx +++ b/apps/web/src/components/ConfirmSwapModal/Pending.tsx @@ -137,7 +137,7 @@ export function Pending({ } else if (uniswapXOrder && uniswapXOrder.status === UniswapXOrderStatus.FILLED) { txHash = uniswapXOrder.txHash } else { - return + return undefined } return getExplorerLink(chainId || UniverseChainId.Mainnet, txHash, ExplorerDataType.TRANSACTION) }, [chainId, swapResult, uniswapXOrder]) diff --git a/apps/web/src/components/ConfirmSwapModal/ProgressIndicator.tsx b/apps/web/src/components/ConfirmSwapModal/ProgressIndicator.tsx index c3b892873c6..e3849bc781d 100644 --- a/apps/web/src/components/ConfirmSwapModal/ProgressIndicator.tsx +++ b/apps/web/src/components/ConfirmSwapModal/ProgressIndicator.tsx @@ -1,5 +1,5 @@ import { ConfirmModalState } from 'components/ConfirmSwapModal' -import { Step } from 'components/ConfirmSwapModal/Step' +import { Step, StepDetails } from 'components/ConfirmSwapModal/Step' import { Sign } from 'components/Icons/Sign' import { Swap } from 'components/Icons/Swap' import CurrencyLogo from 'components/Logo/CurrencyLogo' @@ -18,7 +18,7 @@ import { useIsTransactionConfirmed, useSwapTransactionStatus } from 'state/trans import { colors } from 'theme/colors' import { Divider } from 'theme/components' import { UniswapXOrderStatus } from 'types/uniswapx' -import { StepDetails, StepStatus } from 'uniswap/src/components/ConfirmSwapModal/Step' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' import { uniswapUrls } from 'uniswap/src/constants/urls' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { t } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/ConfirmSwapModal/Step.tsx b/apps/web/src/components/ConfirmSwapModal/Step.tsx index 23b5129b5ff..483bca8c887 100644 --- a/apps/web/src/components/ConfirmSwapModal/Step.tsx +++ b/apps/web/src/components/ConfirmSwapModal/Step.tsx @@ -5,7 +5,28 @@ import Row, { RowBetween } from 'components/deprecated/Row' import styled, { Keyframes, keyframes } from 'lib/styled-components' import { ReactElement, useEffect, useState } from 'react' import { ExternalLink, ThemedText } from 'theme/components' -import { StepDetails, StepStatus } from 'uniswap/src/components/ConfirmSwapModal/Step' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' + +export interface StepDetails { + // Left-justified icon representing the step and grayed out when step is not active + icon: ReactElement + // Ripple animation around the icon of the currently active step (use color extraction to select) + rippleColor?: string + // Text shown before the step becomes active + previewTitle: string + // Text shown when the step is active and awaiting user input + actionRequiredTitle: string | ReactElement + // Text shown when user input has been accepted and step has yet to complete + inProgressTitle?: string + // Amount of time in seconds the user has to take action on a step (e.g. UniswapX exclusivity window) + timeToStart?: number + // Text shown when timeToStart is exceeded (countdown reaches zero) + delayedStartTitle?: string + // Anchor text displayed for the Learn-More link + learnMoreLinkText?: string + // URL for Learn-More link (opened in new tab) + learnMoreLinkHref?: string +} const ringAnimation = keyframes` 0% { @@ -114,7 +135,7 @@ export function Step({ stepStatus, stepDetails }: { stepStatus: StepStatus; step setSecondsRemaining(stepDetails.timeToStart) } else { setSecondsRemaining(null) - return + return undefined } const timer = setInterval(() => { diff --git a/apps/web/src/components/ConfirmSwapModal/index.tsx b/apps/web/src/components/ConfirmSwapModal/index.tsx index 57055b11742..3d731feb9e3 100644 --- a/apps/web/src/components/ConfirmSwapModal/index.tsx +++ b/apps/web/src/components/ConfirmSwapModal/index.tsx @@ -9,7 +9,6 @@ import { MODAL_TRANSITION_DURATION } from 'components/Modal' import { AutoColumn } from 'components/deprecated/Column' import { SwapDetails } from 'components/swap/SwapDetails' import { SwapPreview } from 'components/swap/SwapPreview' -import { Field } from 'components/swap/constants' import { useConfirmModalState } from 'hooks/useConfirmModalState' import { Allowance, AllowanceState } from 'hooks/usePermit2Allowance' import { SwapResult } from 'hooks/useSwapCallback' @@ -26,6 +25,7 @@ import { FadePresence } from 'theme/components/FadePresence' import { UniswapXOrderStatus } from 'types/uniswapx' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { CurrencyField } from 'uniswap/src/types/currency' import { SignatureExpiredError, UniswapXv2HardQuoteError } from 'utils/errors' import { formatSwapPriceUpdatedEventProperties } from 'utils/loggingFormatters' import { didUserReject } from 'utils/swapErrorToUserReadableMessage' @@ -75,7 +75,7 @@ export function ConfirmSwapModal({ clearSwapState: () => void onAcceptChanges?: () => void onConfirm: () => void - onCurrencySelection: (field: Field, currency: Currency) => void + onCurrencySelection: (field: CurrencyField, currency: Currency) => void onDismiss: () => void onXV2RetryWithClassic?: () => void }) { @@ -121,7 +121,7 @@ export function ConfirmSwapModal({ return approvalError } if (swapError instanceof SignatureExpiredError) { - return + return undefined } if (swapError instanceof UniswapXv2HardQuoteError) { return PendingModalError.XV2_HARD_QUOTE_ERROR @@ -129,7 +129,7 @@ export function ConfirmSwapModal({ if (swapError && !didUserReject(swapError)) { return PendingModalError.CONFIRMATION_ERROR } - return + return undefined }, [approvalError, swapError]) // Determine which view to show based on confirm modal state and other conditions diff --git a/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputLabel.tsx b/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputLabel.tsx index 13bdb191137..325d07eeb99 100644 --- a/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputLabel.tsx +++ b/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputLabel.tsx @@ -1,7 +1,7 @@ import { Currency } from '@uniswap/sdk-core' import CurrencyLogo from 'components/Logo/CurrencyLogo' import Row from 'components/deprecated/Row' -import { PrefetchBalancesWrapper } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import styled from 'lib/styled-components' import { ClickableStyle, ThemedText } from 'theme/components' import { Text } from 'ui/src' diff --git a/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputPanel.tsx b/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputPanel.tsx index 473e3750233..82d4c46fe63 100644 --- a/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputPanel.tsx +++ b/apps/web/src/components/CurrencyInputPanel/LimitPriceInputPanel/LimitPriceInputPanel.tsx @@ -12,7 +12,7 @@ import { StyledNumericalInput } from 'components/NumericalInput' import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal' import Row from 'components/deprecated/Row' import { parseUnits } from 'ethers/lib/utils' -import { PrefetchBalancesWrapper } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import JSBI from 'jsbi' import styled from 'lib/styled-components' import { ReversedArrowsIcon } from 'nft/components/icons' diff --git a/apps/web/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx b/apps/web/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx index 5deac4befab..6f2f903d893 100644 --- a/apps/web/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx +++ b/apps/web/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx @@ -14,7 +14,7 @@ import Tooltip from 'components/Tooltip' import { AutoColumn } from 'components/deprecated/Column' import { RowBetween, RowFixed } from 'components/deprecated/Row' import { useIsSupportedChainId } from 'constants/chains' -import { PrefetchBalancesWrapper } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useAccount } from 'hooks/useAccount' import styled, { useTheme } from 'lib/styled-components' import ms from 'ms' diff --git a/apps/web/src/components/CurrencyInputPanel/index.tsx b/apps/web/src/components/CurrencyInputPanel/index.tsx index abbf11eefcb..6f2f7b090d7 100644 --- a/apps/web/src/components/CurrencyInputPanel/index.tsx +++ b/apps/web/src/components/CurrencyInputPanel/index.tsx @@ -11,7 +11,7 @@ import { Input as NumericalInput } from 'components/NumericalInput' import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal' import { RowBetween, RowFixed } from 'components/deprecated/Row' import { useIsSupportedChainId } from 'constants/chains' -import { PrefetchBalancesWrapper } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useAccount } from 'hooks/useAccount' import styled, { useTheme } from 'lib/styled-components' import { darken } from 'polished' diff --git a/apps/web/src/components/DropdownSelector/index.tsx b/apps/web/src/components/DropdownSelector/index.tsx index 4b872c9ec22..fe2392c953c 100644 --- a/apps/web/src/components/DropdownSelector/index.tsx +++ b/apps/web/src/components/DropdownSelector/index.tsx @@ -2,7 +2,6 @@ import FilterButton from 'components/DropdownSelector/FilterButton' import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useRef } from 'react' -import { NAV_HEIGHT } from 'theme' import { AnimatePresence, Flex, @@ -17,6 +16,7 @@ import { import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' import { zIndices } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme/iconSizes' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' export const InternalMenuItem = styled(Text, { display: 'flex', @@ -156,7 +156,7 @@ export function DropdownSelector({ isOpen={isOpen && isSheet} onClose={() => toggleOpen(false)} {...dropdownStyle} - maxHeight={`calc(100dvh - ${NAV_HEIGHT}px)`} + maxHeight={`calc(100dvh - ${INTERFACE_NAV_HEIGHT}px)`} > {internalMenuItems} diff --git a/apps/web/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/apps/web/src/components/FeatureFlagModal/FeatureFlagModal.tsx index 4588dc6e72a..88facb1ce2d 100644 --- a/apps/web/src/components/FeatureFlagModal/FeatureFlagModal.tsx +++ b/apps/web/src/components/FeatureFlagModal/FeatureFlagModal.tsx @@ -223,6 +223,10 @@ export default function FeatureFlagModal() { flag={FeatureFlags.IndicativeSwapQuotes} label="[Universal Swap Flow Only] Enable Quick Routes" /> + { + setCreateFeeValue('') + setCreateModeEnabled(false) + setFeeTierSearchModalOpen(false) + } + const { t } = useTranslation() + const [searchValue, setSearchValue] = useState('') + const [createFeeValue, setCreateFeeValue] = useState('') + const [createModeEnabled, setCreateModeEnabled] = useState(false) + const { formatPercent } = useFormatter() + const [autoDecrementing, setAutoDecrementing] = useState(false) + const [autoIncrementing, setAutoIncrementing] = useState(false) + const [holdDuration, setHoldDuration] = useState(0) + const hiddenObserver = useResizeObserver() + + useEffect(() => { + let interval: NodeJS.Timeout + let holdTimeout: NodeJS.Timeout + const baseInterval = 100 + let currentInterval = baseInterval + + if (autoDecrementing || autoIncrementing) { + holdTimeout = setTimeout(() => { + setHoldDuration((prev) => prev + 1) + }, ms('1s')) + + if (holdDuration >= 2) { + currentInterval = baseInterval / 2 + } + if (holdDuration >= 4) { + currentInterval = baseInterval / 4 + } + if (holdDuration >= 6) { + currentInterval = baseInterval / 8 + } + + interval = setInterval(() => { + setCreateFeeValue((prev) => { + let newValue = parseFloat(prev) + if (autoDecrementing) { + newValue -= 0.01 + if (newValue < 0) { + return '0' + } + } else if (autoIncrementing) { + newValue += 0.01 + if (newValue > 100) { + return '100' + } + } + return newValue.toFixed(2) + }) + }, currentInterval) + + return () => { + clearInterval(interval) + clearTimeout(holdTimeout) + } + } + + return () => { + clearInterval(interval) + clearTimeout(holdTimeout) + setHoldDuration(0) // Reset hold duration on release + } + }, [autoDecrementing, autoIncrementing, holdDuration]) + + // TODO(WEB-4920): use tiers from Positions API for this specific pool, not presets. + const feeTiers = [FeeAmount.LOWEST, FeeAmount.LOW, FeeAmount.MEDIUM, FeeAmount.HIGH] + + return ( + + + + {createModeEnabled && ( + setCreateModeEnabled(false)}> + + + )} + + {createModeEnabled ? t('fee.tier.create') : t('fee.tier.select')} + + + + + {createModeEnabled ? ( + + + {t('fee.tier.create.description')} + + + { + setAutoDecrementing(true) + }} + onPressOut={() => { + setAutoDecrementing(false) + }} + onPress={() => { + setCreateFeeValue((prev) => { + if (!prev || prev === '') { + return '0' + } + const newValue = parseFloat(prev) - 0.01 + if (newValue < 0) { + return '0' + } + return newValue.toFixed(2) + }) + }} + {...ClickableTamaguiStyle} + > + - + + + + { + if (parseInt(input) > 100) { + setCreateFeeValue('100') + } else { + setCreateFeeValue(input) + } + }} + placeholder="0" + maxDecimals={2} + maxLength={4} + $width={createFeeValue && hiddenObserver.width ? hiddenObserver.width + 1 : undefined} + /> + % + {createFeeValue} + + + { + setAutoIncrementing(true) + }} + onPressOut={() => { + setAutoIncrementing(false) + }} + onPress={() => { + setCreateFeeValue((prev) => { + if (!prev || prev === '') { + return '0.01' + } + const newValue = parseFloat(prev) + 0.01 + if (newValue > 100) { + return '100' + } + return newValue.toFixed(2) + }) + }} + {...ClickableTamaguiStyle} + > + + + + + {/* TODO(WEB-4920): search existing fee tiers for a match and optionally show this, with real TVL value */} + {/* + {t('fee.tier.alreadyExists', { formattedTVL: '$289.6K' })} + */} + {/* TODO(WEB-4920): search existing fee tiers for close matches and optionally similar list */} + + + + ) : ( + <> + + + { + setSearchValue(event.target.value) + }} + value={searchValue} + /> + + + {/* TODO(WEB-4920): filter fee tiers based on search term */} + {feeTiers.map((feeTier) => ( + { + setPositionState((prevState) => ({ ...prevState, fee: feeTier })) + onClose() + }} + > + + {formatPercent(new Percent(feeTier, 1000000))} + + {/* TODO(WEB-4920): use real data from positions API */} + + $289.6K TVL + + + {t('fee.tier.percent.select', { percentage: 4 })} + + + + {feeTier === selectedFee && } + + ))} + + + + {t('fee.tier.missing.description')} + + + + + )} + + + ) +} diff --git a/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx b/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx new file mode 100644 index 00000000000..ea07a8e2aed --- /dev/null +++ b/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx @@ -0,0 +1,63 @@ +import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +import { useCurrencyInfo } from 'hooks/Tokens' +import { Flex, Text } from 'ui/src' +import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' + +export function LiquidityPositionAmountsTile({ + currency0Amount, + currency1Amount, +}: { + currency0Amount: CurrencyAmount + currency1Amount: CurrencyAmount +}) { + // TODO(WEB-4920): skip GraphQL call once backend provides image URLs + const currencyInfo0 = useCurrencyInfo(currency0Amount.currency) + const currencyInfo1 = useCurrencyInfo(currency1Amount.currency) + // TODO(WEB-4920): calculate real values for USD amounts and percentages + return ( + + + + + + {currency0Amount.currency.symbol} + + + + + {currency0Amount.toFixed()} + + + ($0) + + + + 45% + + + + + + + + + {currency1Amount.currency.symbol} + + + + + {currency1Amount.toFixed()} + + + ($0) + + + + 55% + + + + + + ) +} diff --git a/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx b/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx index f847a9396aa..69fb51c4b2e 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx @@ -21,6 +21,7 @@ export function LiquidityPositionCard({ liquidityPosition, ...rest }: { liquidit borderRadius="$rounded20" borderColor="$surface3" width="100%" + overflow="hidden" {...rest} > diff --git a/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx b/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx index 0c65d5ec3cd..4a25637dea7 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx @@ -1,10 +1,11 @@ // eslint-disable-next-line no-restricted-imports import { Position } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges' +import { BadgeData, LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges' import { LiquidityPositionStatusIndicator } from 'components/Liquidity/LiquidityPositionStatusIndicator' import { getProtocolVersionLabel, usePositionInfo } from 'components/Liquidity/utils' import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo' import { Flex, Text } from 'ui/src' +import { DocumentList } from 'ui/src/components/icons/DocumentList' interface LiquidityPositionInfoProps { position: Position @@ -32,7 +33,15 @@ export function LiquidityPositionInfo({ position }: LiquidityPositionInfoProps) } + : undefined, + feeTier ? { label: `${Number(feeTier) / 10000}%` } : undefined, + ].filter(Boolean) as BadgeData[] + } /> diff --git a/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.test.tsx b/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.test.tsx index 7a37d352316..178b9c00a05 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.test.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.test.tsx @@ -1,19 +1,21 @@ import { LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges' import { render } from 'test-utils/render' +const testBadgeData = [{ label: 'test', copyable: true }, { label: 'test2' }] + describe('LiquidityPositionInfoBadges', () => { it('should render with default size', () => { - const { getByText } = render() + const { getByText } = render() expect(getByText('test')).toBeInTheDocument() }) it('should render with small size', () => { - const { getByText } = render() + const { getByText } = render() expect(getByText('test')).toBeInTheDocument() }) - it('should render with multiple labels', () => { - const { getByText } = render() + it('should render with multiple badges', () => { + const { getByText } = render() expect(getByText('test')).toBeInTheDocument() expect(getByText('test2')).toBeInTheDocument() }) diff --git a/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.tsx b/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.tsx index d00db7ea315..7e9b18eba9e 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionInfoBadges.tsx @@ -1,6 +1,11 @@ +import { CopyHelper } from 'theme/components' import { styled, Text } from 'ui/src' +import { isAddress, shortenAddress } from 'utilities/src/addresses' -const PositionInfoBadge = styled(Text, { +export const PositionInfoBadge = styled(Text, { + display: 'flex', + flexDirection: 'row', + gap: '$spacing2', variant: 'body3', color: '$neutral2', backgroundColor: '$surface3', @@ -36,20 +41,41 @@ function getPlacement(index: number, length: number): 'start' | 'middle' | 'end' return length === 1 ? 'only' : index === 0 ? 'start' : index === length - 1 ? 'end' : 'middle' } +export interface BadgeData { + label: string + copyable?: boolean + icon?: JSX.Element +} + export function LiquidityPositionInfoBadges({ - labels, + badges, size = 'default', }: { - labels: string[] + badges: BadgeData[] size: 'small' | 'default' }): JSX.Element { return ( <> - {labels.map((label, index) => ( - - {label} - - ))} + {badges.map(({ label, copyable, icon }, index) => { + const displayLabel = isAddress(label) ? shortenAddress(label) : label + return ( + + {icon} + {copyable ? ( + + {displayLabel} + + ) : ( + displayLabel + )} + + ) + })} ) } diff --git a/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx b/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx new file mode 100644 index 00000000000..52a19a4a9d5 --- /dev/null +++ b/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx @@ -0,0 +1,130 @@ +// eslint-disable-next-line no-restricted-imports +import { PositionStatus } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Currency, Price } from '@uniswap/sdk-core' +import { LiquidityPositionStatusIndicator } from 'components/Liquidity/LiquidityPositionStatusIndicator' +import { useMemo, useState } from 'react' +import { Flex, SegmentedControl, SegmentedControlOption, Text, styled } from 'ui/src' +import { Trans } from 'uniswap/src/i18n' + +const InnerTile = styled(Flex, { + grow: true, + alignItems: 'center', + gap: '$gap8', + borderRadius: '$rounded12', + backgroundColor: '$surface3', + p: '$padding16', +}) + +interface LiquidityPositionPriceRangeTileProps { + status: PositionStatus + minPrice: Price + maxPrice: Price + currentPrice: Price +} + +export function LiquidityPositionPriceRangeTile({ + status, + minPrice, + maxPrice, + currentPrice, +}: LiquidityPositionPriceRangeTileProps) { + const [pricesInverted, setPricesInverted] = useState(false) + const currencyASymbol = currentPrice.baseCurrency.symbol + const currencyBSymbol = currentPrice.quoteCurrency.symbol + + const controlOptions: SegmentedControlOption[] = useMemo(() => { + return [ + { + value: currencyASymbol ?? '', + display: {currencyASymbol}, + }, + { + value: currencyBSymbol ?? '', + display: {currencyBSymbol}, + }, + ] + }, [currencyASymbol, currencyBSymbol]) + + if (!currencyASymbol || !currencyBSymbol) { + throw new Error('LiquidityPositionPriceRangeTile: Currency symbols are required') + } + + const displayMinPrice = pricesInverted ? minPrice.invert() : minPrice + const displayMaxPrice = pricesInverted ? maxPrice.invert() : maxPrice + const displayCurrentPrice = pricesInverted ? currentPrice.invert() : currentPrice + const displayASymbol = pricesInverted ? currencyBSymbol : currencyASymbol + const displayBSymbol = pricesInverted ? currencyASymbol : currencyBSymbol + + return ( + + + + + + + + + { + setPricesInverted(selected !== currencyASymbol) + }} + /> + + + + + + + + {displayMinPrice.toFixed()} + + + + + + + + + + + {displayMaxPrice.toFixed()} + + + + + + + + + + + + {displayCurrentPrice.toFixed()} + + + + + + + ) +} diff --git a/apps/web/src/components/Liquidity/PositionNFT.tsx b/apps/web/src/components/Liquidity/PositionNFT.tsx new file mode 100644 index 00000000000..ebde1e1aa34 --- /dev/null +++ b/apps/web/src/components/Liquidity/PositionNFT.tsx @@ -0,0 +1,79 @@ +import { useRef, useState } from 'react' +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import styled from 'styled-components' + +// snapshots a src img into a canvas +function getSnapshot(src: HTMLImageElement, canvas: HTMLCanvasElement, targetHeight: number) { + const context = canvas.getContext('2d') + + if (context) { + let { width, height } = src + + // src may be hidden and not have the target dimensions + const ratio = width / height + height = targetHeight + width = Math.round(ratio * targetHeight) + + // Ensure crispness at high DPIs + canvas.width = width * devicePixelRatio + canvas.height = height * devicePixelRatio + canvas.style.width = width + 'px' + canvas.style.height = height + 'px' + context.scale(devicePixelRatio, devicePixelRatio) + + context.clearRect(0, 0, width, height) + context.drawImage(src, 0, 0, width, height) + } +} + +const NFTGrid = styled.div` + display: grid; + grid-template: 'overlap'; + min-height: 400px; +` + +const NFTCanvas = styled.canvas` + grid-area: overlap; +` + +const NFTImage = styled.img` + grid-area: overlap; + height: 400px; + /* Ensures SVG appears on top of canvas. */ + z-index: 1; +` + +export function PositionNFT({ image, height: targetHeight }: { image: string; height: number }) { + const [animate, setAnimate] = useState(false) + + const canvasRef = useRef(null) + const imageRef = useRef(null) + + return ( + { + setAnimate(true) + }} + onMouseLeave={() => { + // snapshot the current frame so the transition to the canvas is smooth + if (imageRef.current && canvasRef.current) { + getSnapshot(imageRef.current, canvasRef.current, targetHeight) + } + setAnimate(false) + }} + > + + + ) +} diff --git a/apps/web/src/components/Liquidity/utils.tsx b/apps/web/src/components/Liquidity/utils.tsx index 2cb1c8cfcdb..44b94557871 100644 --- a/apps/web/src/components/Liquidity/utils.tsx +++ b/apps/web/src/components/Liquidity/utils.tsx @@ -6,12 +6,9 @@ import { Token as RestToken, } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' -import { Position as V3PositionSDK } from '@uniswap/v3-sdk' -import { usePool } from 'hooks/usePools' import { useMemo } from 'react' import { useAppSelector } from 'state/hooks' import { AppTFunction } from 'ui/src/i18n/types' -import { shortenAddress } from 'utilities/src/addresses' export function getProtocolVersionLabel(version: ProtocolVersion): string | undefined { switch (version) { @@ -25,6 +22,23 @@ export function getProtocolVersionLabel(version: ProtocolVersion): string | unde return undefined } +export function getProtocolVersionFromString(version?: string): ProtocolVersion { + if (!version) { + return ProtocolVersion.V4 + } + + switch (version.toLowerCase()) { + case 'v2': + return ProtocolVersion.V2 + case 'v3': + return ProtocolVersion.V3 + case 'v4': + return ProtocolVersion.V4 + } + + return ProtocolVersion.V4 +} + export function getProtocolStatusLabel(status: PositionStatus, t: AppTFunction): string | undefined { switch (status) { case PositionStatus.IN_RANGE: @@ -37,7 +51,10 @@ export function getProtocolStatusLabel(status: PositionStatus, t: AppTFunction): return undefined } -function parseRestToken(token: RestToken): Currency { +function parseRestToken(token?: RestToken): Token | undefined { + if (!token) { + return undefined + } return new Token(token.chainId, token.address, token.decimals, token.symbol) } @@ -49,27 +66,27 @@ export type PositionInfo = { currency1Amount: CurrencyAmount feeTier?: string v4hook?: string + liquidityToken?: Token } export function usePositionInfo(position?: Position): PositionInfo | undefined { // TODO(WEB-4920): remove this as the API should return the needed information - make this function a synchronous non-hook. // Optimistically fetch the v3Pool, which should be undefined for non-v3 positions - const [, v3Pool] = usePool( - (position as any)?.v3Position?.token0 ? parseRestToken((position as any).v3Position.token0) : undefined, - (position as any)?.v3Position?.token1 ? parseRestToken((position as any).v3Position.token1) : undefined, - parseInt((position as any)?.v3Position?.feeTier), - ) + // const [, v3Pool] = usePool( + // (position as any)?.v3Position?.token0 ? parseRestToken((position as any).v3Position.token0) : undefined, + // (position as any)?.v3Position?.token1 ? parseRestToken((position as any).v3Position.token1) : undefined, + // parseInt((position as any)?.v3Position?.feeTier), + // ) return useMemo(() => { - if (!position) { + if (!position?.position) { return undefined - } else if ((position as any).v2Pair) { - const v2Pair = (position as any).v2Pair - if (!v2Pair.token0 || !v2Pair.token1) { - return undefined - } - + } else if (position.position.case === 'v2Pair') { + const v2Pair = position.position.value const token0 = parseRestToken(v2Pair.token0) const token1 = parseRestToken(v2Pair.token1) + if (!token0 || !token1) { + return undefined + } return { status: position.status, @@ -77,30 +94,32 @@ export function usePositionInfo(position?: Position): PositionInfo | undefined { v4hook: undefined, version: position.protocolVersion, restPosition: position, + liquidityToken: parseRestToken(v2Pair.liquidityToken), // TODO(WEB-4920): test this with a real position and verify the decimals are correct here currency0Amount: CurrencyAmount.fromRawAmount(token0, v2Pair.reserve0.toString()), currency1Amount: CurrencyAmount.fromRawAmount(token1, v2Pair.reserve1.toString()), } - } else if ((position as any).v3Position) { - const v3Position = (position as any).v3Position - if (!v3Position.token0 || !v3Position.token1 || !v3Pool) { + } else if (position.position.case === 'v3Position') { + const v3Position = position.position.value + const token0 = parseRestToken(v3Position.token0) + const token1 = parseRestToken(v3Position.token1) + if (!token0 || !token1) { return undefined } - const token0 = parseRestToken(v3Position.token0) - const token1 = parseRestToken(v3Position.token1) // eslint-disable-next-line @typescript-eslint/no-unused-vars - const v3PositionSDK = new V3PositionSDK({ - pool: v3Pool, - liquidity: v3Position.liquidity, - tickLower: v3Position.tickLower, - tickUpper: v3Position.tickUpper, - }) + // const v3PositionSDK = new V3PositionSDK({ + // pool: v3Pool, + // liquidity: v3Position.liquidity, + // tickLower: parseInt(v3Position.tickLower), + // tickUpper: parseInt(v3Position.tickUpper), + // }) return { status: position.status, feeTier: v3Position.feeTier, version: position.protocolVersion, v4hook: undefined, + liquidityToken: undefined, restPosition: position, // TODO(WEB-4920): test this with a real position and use instead of the hardcoded amounts // currency0Amount: v3PositionSDK.amount0, @@ -109,19 +128,19 @@ export function usePositionInfo(position?: Position): PositionInfo | undefined { currency1Amount: CurrencyAmount.fromRawAmount(token1, '1'), } } else { - const v4Position = (position as any).v4Position - if (!v4Position.poolPosition?.token0 || !v4Position.poolPosition?.token1) { + const v4Position = position.position.value + const token0 = parseRestToken(v4Position?.poolPosition?.token0) + const token1 = parseRestToken(v4Position?.poolPosition?.token1) + if (!token0 || !token1) { return undefined } - const token0 = parseRestToken(v4Position.poolPosition.token0) - const token1 = parseRestToken(v4Position.poolPosition.token1) - return { status: position.status, feeTier: undefined, version: position.protocolVersion, - v4hook: v4Position.hooks[0]?.address ? shortenAddress(v4Position.hooks[0].address) : undefined, + v4hook: v4Position?.hooks[0]?.address, + liquidityToken: undefined, restPosition: position, currency0Amount: CurrencyAmount.fromRawAmount( token0, @@ -133,7 +152,7 @@ export function usePositionInfo(position?: Position): PositionInfo | undefined { ), } } - }, [position, v3Pool]) + }, [position]) } /** diff --git a/apps/web/src/components/Logo/DoubleLogo.tsx b/apps/web/src/components/Logo/DoubleLogo.tsx index b909a86b025..c4200ff040e 100644 --- a/apps/web/src/components/Logo/DoubleLogo.tsx +++ b/apps/web/src/components/Logo/DoubleLogo.tsx @@ -89,7 +89,7 @@ export const SingleLogoContainer = styled.div<{ size: number }>` const DoubleLogoContainer = styled.div<{ size: number }>` ${logoContainerCss} img { - width: ${({ size }) => size / 2}px; + width: ${({ size }) => size / 2 - 1}px; height: ${({ size }) => size}px; object-fit: cover; } diff --git a/apps/web/src/components/NavBar/NavDropdown/NavDropdown.tsx b/apps/web/src/components/NavBar/NavDropdown/NavDropdown.tsx index 89f91ad3ce2..d3a028896c0 100644 --- a/apps/web/src/components/NavBar/NavDropdown/NavDropdown.tsx +++ b/apps/web/src/components/NavBar/NavDropdown/NavDropdown.tsx @@ -1,6 +1,6 @@ import { ReactNode, RefObject } from 'react' -import { NAV_HEIGHT } from 'theme' import { Flex, Popover, WebBottomSheet, styled, useScrollbarStyles, useShadowPropsMedium } from 'ui/src' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' const NavDropdownContent = styled(Flex, { borderRadius: '$rounded16', @@ -8,14 +8,17 @@ const NavDropdownContent = styled(Flex, { borderStyle: 'solid', borderColor: '$surface2', backgroundColor: '$surface1', - overflow: 'scroll', - maxHeight: `calc(100dvh - ${NAV_HEIGHT * 2}px)`, + maxHeight: `calc(100dvh - ${INTERFACE_NAV_HEIGHT * 2}px)`, $sm: { width: '100%', borderRadius: '$none', borderWidth: 0, shadowColor: '$transparent', - maxHeight: `calc(100dvh - ${NAV_HEIGHT}px)`, + maxHeight: `calc(100dvh - ${INTERFACE_NAV_HEIGHT}px)`, + }, + '$platform-web': { + overflowY: 'auto', + overflowX: 'hidden', }, }) diff --git a/apps/web/src/components/NavBar/SearchBar/SuggestionRow.tsx b/apps/web/src/components/NavBar/SearchBar/SuggestionRow.tsx index 209c654a7c8..c268316741c 100644 --- a/apps/web/src/components/NavBar/SearchBar/SuggestionRow.tsx +++ b/apps/web/src/components/NavBar/SearchBar/SuggestionRow.tsx @@ -11,7 +11,7 @@ import { getTokenDetailsURL, supportedChainIdFromGQLChain } from 'graphql/data/u import styled, { css } from 'lib/styled-components' import { searchGenieCollectionToTokenSearchResult, searchTokenToTokenSearchResult } from 'lib/utils/searchBar' import { GenieCollection } from 'nft/types' -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useDispatch } from 'react-redux' import { Link, useNavigate } from 'react-router-dom' import { EllipsisStyle, ThemedText } from 'theme/components' @@ -23,6 +23,7 @@ import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { InterfaceSearchResultSelectionProperties } from 'uniswap/src/features/telemetry/types' import { Trans, useTranslation } from 'uniswap/src/i18n' import { UniverseChainId } from 'uniswap/src/types/chains' +import { shortenAddress } from 'uniswap/src/utils/addresses' import { NumberType, useFormatter } from 'utils/formatNumbers' const PriceChangeContainer = styled.div` @@ -49,16 +50,13 @@ const SuggestionRowStyles = css<{ $isFocused: boolean }>` background: ${theme.surface2}; `} ` + const StyledLink = styled(Link)` ${SuggestionRowStyles} ` const SkeletonSuggestionRow = styled.div` ${SuggestionRowStyles} ` -const PrimaryContainer = styled(Column)` - align-items: flex-start; - width: 90%; -` const CollectionImageStyles = css` width: 36px; height: 36px; @@ -147,6 +145,14 @@ export function SuggestionRow({ } }, [toggleOpen, isHovered, suggestion, navigate, handleClick, path]) + const shortenedAddress = useMemo(() => { + if (isToken && suggestion.address && suggestion.address !== NATIVE_CHAIN_ID) { + return shortenAddress(suggestion.address) + } + + return null + }, [suggestion, isToken]) + return ( isHovered && setHoveredIndex(undefined)} data-testid={isToken ? `searchbar-token-row-${suggestion.chain}-${suggestion.address ?? NATIVE_CHAIN_ID}` : ''} > - + {isToken ? ( setBrokenCollectionImage(true)} /> )} - - + + {suggestion.name} {isToken ? : suggestion.isVerified && } - - {isToken - ? suggestion.symbol - : t('search.results.count', { - count: suggestion?.stats?.total_supply ?? 0, - })} - - + + + {isToken + ? suggestion.symbol + : t('search.results.count', { + count: suggestion?.stats?.total_supply ?? 0, + })} + + {shortenedAddress && ( + + {shortenedAddress} + + )} + + diff --git a/apps/web/src/components/NavBar/SearchBar/__snapshots__/SearchBarDropdown.test.tsx.snap b/apps/web/src/components/NavBar/SearchBar/__snapshots__/SearchBarDropdown.test.tsx.snap index 993bca0ec32..f0ff210e469 100644 --- a/apps/web/src/components/NavBar/SearchBar/__snapshots__/SearchBarDropdown.test.tsx.snap +++ b/apps/web/src/components/NavBar/SearchBar/__snapshots__/SearchBarDropdown.test.tsx.snap @@ -228,7 +228,7 @@ exports[`disable nft on searchbar dropdown should not render popular nft collect + ) : isLogIn ? ( + + ) : ( + + ) +} diff --git a/apps/web/src/components/NavBar/index.tsx b/apps/web/src/components/NavBar/index.tsx index 4451f9a43cd..6541a17b945 100644 --- a/apps/web/src/components/NavBar/index.tsx +++ b/apps/web/src/components/NavBar/index.tsx @@ -20,15 +20,16 @@ import { useIsSwapPage } from 'hooks/useIsSwapPage' import styled, { css } from 'lib/styled-components' import { useProfilePageState } from 'nft/hooks' import { ProfilePageStateType } from 'nft/types' -import { BREAKPOINTS, NAV_HEIGHT } from 'theme' +import { BREAKPOINTS } from 'theme' import { Z_INDEX } from 'theme/zIndex' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' const Nav = styled.nav` padding: 0px 12px; width: 100%; - height: ${NAV_HEIGHT}px; + height: ${INTERFACE_NAV_HEIGHT}px; z-index: ${Z_INDEX.sticky}; display: flex; align-items: center; diff --git a/apps/web/src/components/PercentInput.tsx b/apps/web/src/components/PercentInput.tsx index 41208a1b646..0fcb8716092 100644 --- a/apps/web/src/components/PercentInput.tsx +++ b/apps/web/src/components/PercentInput.tsx @@ -3,18 +3,18 @@ import { NumericalInputFontStyle } from 'pages/Swap/common/shared' import React, { forwardRef } from 'react' // eslint-disable-next-line @typescript-eslint/no-restricted-imports import styled from 'styled-components' -import { escapeRegExp } from 'utilities/src/primitives/string' import { useFormatterLocales } from 'utils/formatNumbers' -const inputRegex = RegExp(`^\\d*$`) +const inputRegex = RegExp(`^\\d*(\\.\\d{0,2})?$`) const PercentInput = forwardRef( ({ value, onUserInput, placeholder, testId, ...rest }: InputProps, ref) => { const { formatterLocale } = useFormatterLocales() const enforcer = (nextUserInput: string) => { - if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) { - onUserInput(nextUserInput) + const sanitizedInput = nextUserInput.replace(/,/g, '.') // Normalize the input + if (sanitizedInput === '' || inputRegex.test(sanitizedInput)) { + onUserInput(sanitizedInput) } } @@ -27,12 +27,14 @@ const PercentInput = forwardRef( return ( { - enforcer(event.target.value.replace(/,/g, '.')) + enforcer(event.target.value) }} // universal input options inputMode="numeric" @@ -40,10 +42,8 @@ const PercentInput = forwardRef( autoCorrect="off" // text-specific options type="text" - pattern="^[0-9]*$" + pattern="^\\d*(\\.\\d{0,2})?$" placeholder={placeholder || '0'} - minLength={1} - maxLength={2} spellCheck="false" /> ) diff --git a/apps/web/src/components/Pools/PoolDetails/ChartSection/index.tsx b/apps/web/src/components/Pools/PoolDetails/ChartSection/index.tsx index 853132593ca..6c5f5084078 100644 --- a/apps/web/src/components/Pools/PoolDetails/ChartSection/index.tsx +++ b/apps/web/src/components/Pools/PoolDetails/ChartSection/index.tsx @@ -118,6 +118,7 @@ function usePDPChartState( const volumeQuery = usePDPVolumeChartData(variables) return useMemo(() => { + // eslint-disable-next-line consistent-return const activeQuery = (() => { switch (chartType) { case ChartType.PRICE: @@ -163,6 +164,7 @@ export default function ChartSection(props: ChartSectionProps) { // TODO(WEB-3740): Integrate BE tick query, remove special casing for liquidity chart const loading = props.loading || (activeQuery.chartType !== ChartType.LIQUIDITY ? activeQuery?.loading : false) + // eslint-disable-next-line consistent-return const ChartBody = (() => { if (!currencyA || !currencyB || !props.poolData || !props.chain) { return diff --git a/apps/web/src/components/Pools/PoolDetails/PoolDetailsStatsButtons.tsx b/apps/web/src/components/Pools/PoolDetails/PoolDetailsStatsButtons.tsx index f5ea57787f5..5c50c73a8ca 100644 --- a/apps/web/src/components/Pools/PoolDetails/PoolDetailsStatsButtons.tsx +++ b/apps/web/src/components/Pools/PoolDetails/PoolDetailsStatsButtons.tsx @@ -10,7 +10,7 @@ import { LoadingBubble } from 'components/Tokens/loading' import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage' import { chainIdToBackendChain, SupportedInterfaceChainId } from 'constants/chains' import { getPriorityWarning, StrongWarning, useTokenWarning } from 'constants/deprecatedTokenSafety' -import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { gqlToCurrency } from 'graphql/data/util' import { useScreenSize } from 'hooks/screenSize/useScreenSize' import { useAccount } from 'hooks/useAccount' diff --git a/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap b/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap index 01346ecfcff..7240e786740 100644 --- a/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap +++ b/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap @@ -364,7 +364,7 @@ exports[`PoolDetailsHeader renders header text correctly 1`] = ` } .c4 img { - width: 16px; + width: 15px; height: 32px; object-fit: cover; } diff --git a/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsLink.test.tsx.snap b/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsLink.test.tsx.snap index b8b22d91c8a..7c29c102b4f 100644 --- a/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsLink.test.tsx.snap +++ b/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsLink.test.tsx.snap @@ -14,7 +14,7 @@ exports[`PoolDetailsHeader renders link for pool address 1`] = ` } .c5 img { - width: 10px; + width: 9px; height: 20px; object-fit: cover; } diff --git a/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsPositionTable.test.tsx.snap b/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsPositionTable.test.tsx.snap index 081653ca11b..5187f3bf461 100644 --- a/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsPositionTable.test.tsx.snap +++ b/apps/web/src/components/Pools/PoolDetails/__snapshots__/PoolDetailsPositionTable.test.tsx.snap @@ -14,7 +14,7 @@ exports[`PoolDetailsPositionsTable renders with PositionStatus Closed 1`] = ` } .c5 img { - width: 8px; + width: 7px; height: 16px; object-fit: cover; } @@ -325,7 +325,7 @@ exports[`PoolDetailsPositionsTable renders with PositionStatus In Range 1`] = ` } .c5 img { - width: 8px; + width: 7px; height: 16px; object-fit: cover; } @@ -611,7 +611,7 @@ exports[`PoolDetailsPositionsTable renders with PositionStatus Out Of Range 1`] } .c5 img { - width: 8px; + width: 7px; height: 16px; object-fit: cover; } diff --git a/apps/web/src/components/Pools/PoolTable/__snapshots__/PoolTable.test.tsx.snap b/apps/web/src/components/Pools/PoolTable/__snapshots__/PoolTable.test.tsx.snap index 8383d1c1eda..cf80be53796 100644 --- a/apps/web/src/components/Pools/PoolTable/__snapshots__/PoolTable.test.tsx.snap +++ b/apps/web/src/components/Pools/PoolTable/__snapshots__/PoolTable.test.tsx.snap @@ -14,7 +14,7 @@ exports[`PoolTable renders data filled state 1`] = ` } .c4 img { - width: 14px; + width: 13px; height: 28px; object-fit: cover; } diff --git a/apps/web/src/components/Popups/PopupContent.tsx b/apps/web/src/components/Popups/PopupContent.tsx index 7f22480dea0..9d5f11c2e21 100644 --- a/apps/web/src/components/Popups/PopupContent.tsx +++ b/apps/web/src/components/Popups/PopupContent.tsx @@ -8,6 +8,7 @@ import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types' import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' import PortfolioRow from 'components/AccountDrawer/MiniPortfolio/PortfolioRow' import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled' +import { LoaderV3 } from 'components/Icons/LoadingSpinner' import Column, { AutoColumn } from 'components/deprecated/Column' import { AutoRow } from 'components/deprecated/Row' import { SupportedInterfaceChainId, useIsSupportedChainId } from 'constants/chains' @@ -16,6 +17,7 @@ import { X } from 'react-feather' import { useOrder } from 'state/signatures/hooks' import { useTransaction } from 'state/transactions/hooks' import { EllipsisStyle, ThemedText } from 'theme/components' +import { Flex, useSporeColors } from 'ui/src' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { Trans } from 'uniswap/src/i18n' @@ -102,13 +104,16 @@ const Descriptor = styled(ThemedText.BodySmall)` type ActivityPopupContentProps = { activity: Activity; onClick: () => void; onClose: () => void } function ActivityPopupContent({ activity, onClick, onClose }: ActivityPopupContentProps) { const success = activity.status === TransactionStatus.Confirmed && !activity.cancelled + const pending = activity.status === TransactionStatus.Pending + + const showPortfolioLogo = success || pending || !!activity.offchainOrderDetails + const colors = useSporeColors() return ( - {activity.descriptor}} onClick={onClick} /> + {pending ? ( + + + + ) : ( + + )} ) } diff --git a/apps/web/src/components/SearchModal/CurrencySearchModal.tsx b/apps/web/src/components/SearchModal/CurrencySearchModal.tsx index b245a930055..06963564612 100644 --- a/apps/web/src/components/SearchModal/CurrencySearchModal.tsx +++ b/apps/web/src/components/SearchModal/CurrencySearchModal.tsx @@ -4,9 +4,9 @@ import TokenSafety from 'components/TokenSafety' import useLast from 'hooks/useLast' import { memo, useCallback, useEffect, useState } from 'react' import { useUserAddedTokens } from 'state/user/userAddedTokens' -import { NAV_HEIGHT } from 'theme' import { AdaptiveWebModal } from 'ui/src' import { TOKEN_SELECTOR_WEB_MAX_WIDTH } from 'uniswap/src/components/TokenSelector/TokenSelector' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' import { CurrencyField } from 'uniswap/src/types/currency' interface CurrencySearchModalProps { @@ -72,8 +72,8 @@ export default memo(function CurrencySearchModal({ content = ( handleCurrencySelect(warningToken)} - onCancel={() => setModalView(CurrencyModalView.search)} + onAcknowledge={() => handleCurrencySelect(warningToken)} + closeModalOnly={() => setModalView(CurrencyModalView.search)} showCancel={true} /> ) @@ -89,7 +89,7 @@ export default memo(function CurrencySearchModal({ px={0} py={0} flex={1} - $sm={{ height: `calc(100dvh - ${NAV_HEIGHT}px)` }} + $sm={{ height: `calc(100dvh - ${INTERFACE_NAV_HEIGHT}px)` }} > {content} diff --git a/apps/web/src/components/Settings/MultipleRoutingOptions.tsx b/apps/web/src/components/Settings/MultipleRoutingOptions.tsx index 0ff035bc227..4f891e5c113 100644 --- a/apps/web/src/components/Settings/MultipleRoutingOptions.tsx +++ b/apps/web/src/components/Settings/MultipleRoutingOptions.tsx @@ -3,7 +3,7 @@ import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark' import QuestionHelper from 'components/QuestionHelper' import Column from 'components/deprecated/Column' import Row, { RowBetween } from 'components/deprecated/Row' -import { isUniswapXSupportedChain } from 'constants/chains' +import { useIsUniswapXSupportedChain } from 'constants/chains' import { atom, useAtom } from 'jotai' import styled from 'lib/styled-components' import { ReactNode, useCallback } from 'react' @@ -122,7 +122,7 @@ export default function MultipleRoutingOptions({ chainId }: { chainId?: number } const [, setRoutingPreferences] = useAtom(routingPreferencesAtom) const shouldDisableProtocolOptionToggle = !routePreferenceOptions[RoutePreferenceOption.v2] || !routePreferenceOptions[RoutePreferenceOption.v3] - const uniswapXSupportedChain = chainId && isUniswapXSupportedChain(chainId) + const uniswapXSupportedChain = useIsUniswapXSupportedChain(chainId) const handleSetRoutePreferenceOptions = useCallback( (options: RoutePreferenceOptionsType) => { if (options[RoutePreferenceOption.Optimal]) { diff --git a/apps/web/src/components/Settings/index.test.tsx b/apps/web/src/components/Settings/index.test.tsx index 5f52e320bed..76b938ec24e 100644 --- a/apps/web/src/components/Settings/index.test.tsx +++ b/apps/web/src/components/Settings/index.test.tsx @@ -1,6 +1,6 @@ import { Percent } from '@uniswap/sdk-core' import SettingsTab from 'components/Settings/index' -import { isUniswapXSupportedChain, useIsSupportedChainId } from 'constants/chains' +import { useIsSupportedChainId, useIsUniswapXSupportedChain } from 'constants/chains' import { useAccount } from 'hooks/useAccount' import { mocked } from 'test-utils/mocked' import { fireEvent, render, screen, waitFor } from 'test-utils/render' @@ -20,7 +20,7 @@ describe('Settings Tab', () => { }) it('renders routing settings when hideRoutingSettings is false', async () => { - mocked(isUniswapXSupportedChain).mockReturnValue(true) + mocked(useIsUniswapXSupportedChain).mockReturnValue(true) render() const settingsButton = screen.getByTestId('open-settings-dialog-button') @@ -43,7 +43,7 @@ describe('Settings Tab', () => { }) it('does not render routing settings when uniswapx is not enabled', async () => { - mocked(isUniswapXSupportedChain).mockReturnValue(false) + mocked(useIsUniswapXSupportedChain).mockReturnValue(false) render() const settingsButton = screen.getByTestId('open-settings-dialog-button') diff --git a/apps/web/src/components/Settings/index.tsx b/apps/web/src/components/Settings/index.tsx index 50b23347d8c..5acb587d56a 100644 --- a/apps/web/src/components/Settings/index.tsx +++ b/apps/web/src/components/Settings/index.tsx @@ -8,7 +8,7 @@ import MenuButton from 'components/Settings/MenuButton' import MultipleRoutingOptions from 'components/Settings/MultipleRoutingOptions' import RouterPreferenceSettings from 'components/Settings/RouterPreferenceSettings' import TransactionDeadlineSettings from 'components/Settings/TransactionDeadlineSettings' -import { isUniswapXSupportedChain, useIsSupportedChainId } from 'constants/chains' +import { useIsSupportedChainId, useIsUniswapXSupportedChain } from 'constants/chains' import { useIsMobile } from 'hooks/screenSize/useIsMobile' import useDisableScrolling from 'hooks/useDisableScrolling' import { useOnClickOutside } from 'hooks/useOnClickOutside' @@ -136,7 +136,7 @@ export default function SettingsTab({ useDisableScrolling(isOpen) const multipleRoutingOptionsEnabled = useFeatureFlag(FeatureFlags.MultipleRoutingOptions) - const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId) + const uniswapXEnabled = useIsUniswapXSupportedChain(chainId) const showRoutingSettings = Boolean(uniswapXEnabled && !hideRoutingSettings && !multipleRoutingOptionsEnabled) const isChainSupported = useIsSupportedChainId(chainId) diff --git a/apps/web/src/components/Table/index.tsx b/apps/web/src/components/Table/index.tsx index 6638d31b4fc..1479dfdf423 100644 --- a/apps/web/src/components/Table/index.tsx +++ b/apps/web/src/components/Table/index.tsx @@ -30,12 +30,12 @@ import { import useDebounce from 'hooks/useDebounce' import { useEffect, useMemo, useRef, useState } from 'react' import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync' -import { NAV_HEIGHT } from 'theme' import { ThemedText } from 'theme/components' import { FadePresence } from 'theme/components/FadePresence' import { Z_INDEX } from 'theme/zIndex' import Trace from 'uniswap/src/features/telemetry/Trace' import { Trans } from 'uniswap/src/i18n' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' function TableBody({ @@ -154,7 +154,7 @@ export function Table({ useEffect(() => { const scrollableElement = maxHeight ? tableBodyRef.current : window if (scrollableElement === null) { - return + return undefined } const updateScrollPosition = () => { if (scrollableElement instanceof HTMLDivElement) { @@ -203,7 +203,7 @@ export function Table({ }) const headerHeight = useMemo(() => { const header = document.getElementById('AppHeader') - return header?.clientHeight || NAV_HEIGHT + return header?.clientHeight || INTERFACE_NAV_HEIGHT }, []) return ( diff --git a/apps/web/src/components/TokenSafety/TokenSafetyModal.tsx b/apps/web/src/components/TokenSafety/TokenSafetyModal.tsx index e8d4a8d193f..ca2f37091c4 100644 --- a/apps/web/src/components/TokenSafety/TokenSafetyModal.tsx +++ b/apps/web/src/components/TokenSafety/TokenSafetyModal.tsx @@ -8,6 +8,9 @@ import TokenSafety, { TokenSafetyProps } from '.' interface TokenSafetyModalProps extends TokenSafetyProps { isOpen: boolean + onReject?: () => void + onToken0BlockAcknowledged: () => void + onToken1BlockAcknowledged?: () => void } /* TODO(WALL-4625): Clean up and remove this file; is duplicate of packages/uniswap TokenWarningModal.tsx */ @@ -15,9 +18,11 @@ export default function TokenSafetyModal({ isOpen, token0, token1, - onContinue, - onCancel, - onBlocked, + onAcknowledge, + closeModalOnly, + onReject, + onToken0BlockAcknowledged, + onToken1BlockAcknowledged, showCancel, }: TokenSafetyModalProps) { const tokenProtectionEnabled = useFeatureFlag(FeatureFlags.TokenProtection) @@ -35,17 +40,24 @@ export default function TokenSafetyModal({ isVisible={isOpen} currencyInfo0={currencyInfo0} currencyInfo1={currencyInfo1 ?? undefined} - onClose={onBlocked ?? onCancel} - onAccept={onContinue} + onReject={onReject} + onAcknowledge={onAcknowledge} + closeModalOnly={closeModalOnly} + onToken0BlockAcknowledged={onToken0BlockAcknowledged} + onToken1BlockAcknowledged={onToken1BlockAcknowledged} /> ) : ( - + { + onToken0BlockAcknowledged() + onToken1BlockAcknowledged?.() + closeModalOnly() + }} + closeModalOnly={closeModalOnly} showCancel={showCancel} /> diff --git a/apps/web/src/components/TokenSafety/index.tsx b/apps/web/src/components/TokenSafety/index.tsx index 0c438186613..68cd68f3241 100644 --- a/apps/web/src/components/TokenSafety/index.tsx +++ b/apps/web/src/components/TokenSafety/index.tsx @@ -130,13 +130,20 @@ const StyledExternalLink = styled(ExternalLink)` export interface TokenSafetyProps { token0: Token token1?: Token - onContinue: () => void - onCancel: () => void + onAcknowledge: () => void + closeModalOnly: () => void onBlocked?: () => void showCancel?: boolean } -export default function TokenSafety({ token0, token1, onContinue, onCancel, onBlocked, showCancel }: TokenSafetyProps) { +export default function TokenSafety({ + token0, + token1, + onAcknowledge, + closeModalOnly: onClose, + onBlocked, + showCancel, +}: TokenSafetyProps) { const logos = [] const urls = [] @@ -169,7 +176,7 @@ export default function TokenSafety({ token0, token1, onContinue, onCancel, onBl const acknowledge = () => { onDismissToken0() onDismissToken1() - onContinue() + onAcknowledge() } const { heading, description } = getWarningCopy(displayWarning, plural) @@ -199,7 +206,7 @@ export default function TokenSafety({ token0, token1, onContinue, onCancel, onBl @@ -216,7 +223,7 @@ export default function TokenSafety({ token0, token1, onContinue, onCancel, onBl {heading} {description} {learnMoreUrl} - + ) diff --git a/apps/web/src/components/Tokens/TokenDetails/ChartSection/index.tsx b/apps/web/src/components/Tokens/TokenDetails/ChartSection/index.tsx index b8356e75cde..efde7f2bf80 100644 --- a/apps/web/src/components/Tokens/TokenDetails/ChartSection/index.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/ChartSection/index.tsx @@ -84,6 +84,7 @@ export function useCreateTDPChartState(tokenDBAddress: string | undefined, curre return useMemo(() => { const { disableCandlestickUI } = priceQuery + // eslint-disable-next-line consistent-return const activeQuery = (() => { switch (chartType) { case ChartType.PRICE: @@ -110,38 +111,36 @@ export function useCreateTDPChartState(tokenDBAddress: string | undefined, curre export default function ChartSection() { const { activeQuery, timePeriod, priceChartType } = useTDPContext().chartState + // eslint-disable-next-line consistent-return + const getSection = () => { + if (activeQuery.dataQuality === DataQuality.INVALID) { + return ( + } + /> + ) + } + + const stale = activeQuery.dataQuality === DataQuality.STALE + switch (activeQuery.chartType) { + case ChartType.PRICE: + return ( + + ) + case ChartType.VOLUME: + return ( + + ) + case ChartType.TVL: + return + } + } + return (
- {(() => { - if (activeQuery.dataQuality === DataQuality.INVALID) { - return ( - } - /> - ) - } - - const stale = activeQuery.dataQuality === DataQuality.STALE - switch (activeQuery.chartType) { - case ChartType.PRICE: - return ( - - ) - case ChartType.VOLUME: - return ( - - ) - case ChartType.TVL: - return - } - })()} + {getSection()}
) diff --git a/apps/web/src/components/Tokens/TokenDetails/index.tsx b/apps/web/src/components/Tokens/TokenDetails/index.tsx index e1e02432c50..87faf407af1 100644 --- a/apps/web/src/components/Tokens/TokenDetails/index.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/index.tsx @@ -3,7 +3,6 @@ import { Currency } from '@uniswap/sdk-core' import { BreadcrumbNavContainer, BreadcrumbNavLink, CurrentPageBreadcrumb } from 'components/BreadcrumbNav' import { MobileBottomBar, TDPActionTabs } from 'components/NavBar/MobileBottomBar' import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage' -import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import { ActivitySection } from 'components/Tokens/TokenDetails/ActivitySection' import BalanceSummary, { PageChainBalanceSummary } from 'components/Tokens/TokenDetails/BalanceSummary' import ChartSection from 'components/Tokens/TokenDetails/ChartSection' @@ -23,7 +22,7 @@ import { ScrollDirection, useScroll } from 'hooks/useScroll' import deprecatedStyled from 'lib/styled-components' import { Swap } from 'pages/Swap' import { useTDPContext } from 'pages/TokenDetails/TDPContext' -import { PropsWithChildren, useCallback, useMemo, useState } from 'react' +import { PropsWithChildren, useCallback, useMemo } from 'react' import { ChevronRight } from 'react-feather' import { useNavigate } from 'react-router-dom' import { CurrencyState } from 'state/swap/types' @@ -127,47 +126,18 @@ function TDPSwapComponent() { // Other token to prefill the swap form with const initialInputCurrency = useSwapInitialInputCurrency() - const [openTokenSafetyModal, setOpenTokenSafetyModal] = useState(false) - const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike) => void }>() - - const onResolveSwap = useCallback( - (value: boolean) => { - continueSwap?.resolve(value) - setContinueSwap(undefined) - }, - [continueSwap, setContinueSwap], - ) - const isBlockedToken = warning?.canProceed === false - return ( <> -
isBlockedToken && setOpenTokenSafetyModal(true)} - > - -
+ {warning && } - {currency.isToken && ( - onResolveSwap(true)} - onBlocked={() => { - setOpenTokenSafetyModal(false) - }} - onCancel={() => onResolveSwap(false)} - showCancel={true} - /> - )} ) } diff --git a/apps/web/src/components/Tokens/TokenDetails/tables/__snapshots__/TokenDetailsPoolsTable.test.tsx.snap b/apps/web/src/components/Tokens/TokenDetails/tables/__snapshots__/TokenDetailsPoolsTable.test.tsx.snap index 7e357ebf395..3e948723b69 100644 --- a/apps/web/src/components/Tokens/TokenDetails/tables/__snapshots__/TokenDetailsPoolsTable.test.tsx.snap +++ b/apps/web/src/components/Tokens/TokenDetails/tables/__snapshots__/TokenDetailsPoolsTable.test.tsx.snap @@ -14,7 +14,7 @@ exports[`TDPPoolTable renders data filled state 1`] = ` } .c4 img { - width: 14px; + width: 13px; height: 28px; object-fit: cover; } diff --git a/apps/web/src/components/Tokens/TokenTable/VolumeTimeFrameSelector.tsx b/apps/web/src/components/Tokens/TokenTable/VolumeTimeFrameSelector.tsx index 15bfd7e1ad9..e1dad3c24c5 100644 --- a/apps/web/src/components/Tokens/TokenTable/VolumeTimeFrameSelector.tsx +++ b/apps/web/src/components/Tokens/TokenTable/VolumeTimeFrameSelector.tsx @@ -25,6 +25,7 @@ export const DISPLAYS: Record = { [TimePeriod.YEAR]: TimePeriodDisplay.YEAR, } +// eslint-disable-next-line consistent-return export function getTimePeriodFromDisplay(display: TimePeriodDisplay): TimePeriod { switch (display) { case TimePeriodDisplay.HOUR: diff --git a/apps/web/src/components/Tokens/TokenTable/index.tsx b/apps/web/src/components/Tokens/TokenTable/index.tsx index 770ea2c42f2..d046b73f08b 100644 --- a/apps/web/src/components/Tokens/TokenTable/index.tsx +++ b/apps/web/src/components/Tokens/TokenTable/index.tsx @@ -76,7 +76,7 @@ function TokenDescription({ token }: { token: TopToken | TokenStat }) { return ( - {token?.project?.name ?? token?.name} + {token?.name ?? token?.project?.name} { switch (size) { case TooltipSize.ExtraSmall: @@ -102,7 +103,7 @@ export const MouseoverTooltip = memo(function MouseoverTooltip(props: MouseoverT clearTimeout(tooltipTimer) } } - return + return undefined }, [timeout, show]) return ( diff --git a/apps/web/src/components/Web3Provider/index.tsx b/apps/web/src/components/Web3Provider/index.tsx index 95a3560ae42..0c6a75c4b33 100644 --- a/apps/web/src/components/Web3Provider/index.tsx +++ b/apps/web/src/components/Web3Provider/index.tsx @@ -1,7 +1,7 @@ import { QueryClientProvider } from '@tanstack/react-query' import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events' import { recentConnectorIdAtom } from 'components/Web3Provider/constants' -import { queryClient, wagmiConfig } from 'components/Web3Provider/wagmi' +import { queryClient, wagmiConfig } from 'components/Web3Provider/wagmiConfig' import { walletTypeToAmplitudeWalletType } from 'components/Web3Provider/walletConnect' import { useIsSupportedChainId } from 'constants/chains' import { RPC_PROVIDERS } from 'constants/providers' diff --git a/apps/web/src/components/Web3Provider/wagmi.ts b/apps/web/src/components/Web3Provider/wagmiConfig.ts similarity index 100% rename from apps/web/src/components/Web3Provider/wagmi.ts rename to apps/web/src/components/Web3Provider/wagmiConfig.ts diff --git a/apps/web/src/components/Web3Status/index.tsx b/apps/web/src/components/Web3Status/index.tsx index 9e21825fa04..3ba8cd0793c 100644 --- a/apps/web/src/components/Web3Status/index.tsx +++ b/apps/web/src/components/Web3Status/index.tsx @@ -7,7 +7,7 @@ import Loader, { LoaderV3 } from 'components/Icons/LoadingSpinner' import StatusIcon, { IconWrapper } from 'components/Identicon/StatusIcon' import { useAccountIdentifier } from 'components/Web3Status/useAccountIdentifier' import { RowBetween } from 'components/deprecated/Row' -import { PrefetchBalancesWrapper } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { navSearchInputVisibleSize } from 'hooks/screenSize/useScreenSize' import { useAccount } from 'hooks/useAccount' import { atom, useAtom } from 'jotai' diff --git a/apps/web/src/components/addLiquidity/AddLiquidityContext.tsx b/apps/web/src/components/addLiquidity/AddLiquidityContext.tsx index 8309a84b455..c5018cfcd4a 100644 --- a/apps/web/src/components/addLiquidity/AddLiquidityContext.tsx +++ b/apps/web/src/components/addLiquidity/AddLiquidityContext.tsx @@ -1,23 +1,23 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { PositionInfo, useModalLiquidityPositionInfo } from 'components/Liquidity/utils' -import { Field } from 'components/addLiquidity/InputForm' import { useDerivedAddLiquidityInfo } from 'components/addLiquidity/hooks' import { Dispatch, PropsWithChildren, SetStateAction, createContext, useContext, useMemo, useState } from 'react' +import { PositionField } from 'types/position' export interface AddLiquidityState { position?: PositionInfo - exactField: Field + exactField: PositionField exactAmount?: string } const DEFAULT_ADD_LIQUIDITY_STATE = { - exactField: Field.TOKEN0, + exactField: PositionField.TOKEN0, } export interface AddLiquidityInfo { - formattedAmounts?: { [field in Field]?: string } - currencyBalances?: { [field in Field]?: CurrencyAmount } - currencyAmounts?: { [field in Field]?: CurrencyAmount } - currencyAmountsUSDValue?: { [field in Field]?: CurrencyAmount } + formattedAmounts?: { [field in PositionField]?: string } + currencyBalances?: { [field in PositionField]?: CurrencyAmount } + currencyAmounts?: { [field in PositionField]?: CurrencyAmount } + currencyAmountsUSDValue?: { [field in PositionField]?: CurrencyAmount } } interface AddLiquidityContextType { diff --git a/apps/web/src/components/addLiquidity/InputForm.tsx b/apps/web/src/components/addLiquidity/InputForm.tsx index aec154ceee5..d5f7a0e3814 100644 --- a/apps/web/src/components/addLiquidity/InputForm.tsx +++ b/apps/web/src/components/addLiquidity/InputForm.tsx @@ -2,20 +2,16 @@ import { Currency } from '@uniswap/sdk-core' import { AddLiquidityInfo } from 'components/addLiquidity/AddLiquidityContext' import { useCurrencyInfo } from 'hooks/Tokens' import { useState } from 'react' +import { PositionField } from 'types/position' import { Flex } from 'ui/src' import { CurrencyInputPanel } from 'uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel' import { CurrencyField } from 'uniswap/src/types/currency' -export enum Field { - TOKEN0 = 'TOKEN0', - TOKEN1 = 'TOKEN1', -} - type InputFormProps = { token0: Currency token1: Currency - onUserInput: (field: Field, newValue: string) => void - onSetMax: (field: Field, amount: string) => void + onUserInput: (field: PositionField, newValue: string) => void + onSetMax: (field: PositionField, amount: string) => void } & AddLiquidityInfo export function InputForm({ @@ -28,19 +24,19 @@ export function InputForm({ onUserInput, onSetMax, }: InputFormProps) { - const [focusedInputField, setFocusedInputField] = useState(Field.TOKEN0) + const [focusedInputField, setFocusedInputField] = useState(PositionField.TOKEN0) // TODO(WEB-4920): when the backend returns the logo info make sure that there is no call being made // to graphql to retrieve it const token0CurrencyInfo = useCurrencyInfo(token0) const token1CurrencyInfo = useCurrencyInfo(token1) - const handleUserInput = (field: Field) => { + const handleUserInput = (field: PositionField) => { return (newValue: string) => { onUserInput(field, newValue) } } - const handleOnSetMax = (field: Field) => { + const handleOnSetMax = (field: PositionField) => { return (amount: string) => { setFocusedInputField(field) onSetMax(field, amount) @@ -50,37 +46,37 @@ export function InputForm({ undefined} - usdValue={currencyAmountsUSDValue?.[Field.TOKEN0]} - onSetMax={handleOnSetMax(Field.TOKEN0)} - value={formattedAmounts?.[Field.TOKEN0]} - onPressIn={() => setFocusedInputField(Field.TOKEN0)} + usdValue={currencyAmountsUSDValue?.[PositionField.TOKEN0]} + onSetMax={handleOnSetMax(PositionField.TOKEN0)} + value={formattedAmounts?.[PositionField.TOKEN0]} + onPressIn={() => setFocusedInputField(PositionField.TOKEN0)} /> undefined} - usdValue={currencyAmountsUSDValue?.[Field.TOKEN1]} - onSetMax={handleOnSetMax(Field.TOKEN1)} - value={formattedAmounts?.[Field.TOKEN1]} - onPressIn={() => setFocusedInputField(Field.TOKEN1)} + usdValue={currencyAmountsUSDValue?.[PositionField.TOKEN1]} + onSetMax={handleOnSetMax(PositionField.TOKEN1)} + value={formattedAmounts?.[PositionField.TOKEN1]} + onPressIn={() => setFocusedInputField(PositionField.TOKEN1)} /> ) diff --git a/apps/web/src/components/addLiquidity/hooks.tsx b/apps/web/src/components/addLiquidity/hooks.tsx index c57ac513ae7..0a02128fe2a 100644 --- a/apps/web/src/components/addLiquidity/hooks.tsx +++ b/apps/web/src/components/addLiquidity/hooks.tsx @@ -1,31 +1,123 @@ +// eslint-disable-next-line no-restricted-imports +import { PoolPosition } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +import { FeeAmount, Position } from '@uniswap/v3-sdk' import { AddLiquidityInfo, AddLiquidityState } from 'components/addLiquidity/AddLiquidityContext' -import { Field } from 'components/addLiquidity/InputForm' import { useAccount } from 'hooks/useAccount' +import { usePool } from 'hooks/usePools' +import { useV2Pair } from 'hooks/useV2Pairs' import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' +import { useMemo } from 'react' +import { PositionField } from 'types/position' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +function parseV3FeeTier(feeTier: string | undefined): FeeAmount | undefined { + const parsedFee = parseInt(feeTier || '') + + return parsedFee in FeeAmount ? parsedFee : undefined +} + export function useDerivedAddLiquidityInfo(state: AddLiquidityState): AddLiquidityInfo { const account = useAccount() - const { position, exactAmount } = state + const { position: positionInfo, exactAmount, exactField } = state - if (!position) { + if (!positionInfo) { throw new Error('no position available') } - const token0 = position.currency0Amount.currency - const token1 = position.currency1Amount.currency + const token0 = positionInfo.currency0Amount.currency + const token1 = positionInfo.currency1Amount.currency const [token0Balance, token1Balance] = useCurrencyBalances(account.address, [token0, token1]) - const token0CurrencyAmount = tryParseCurrencyAmount(exactAmount, token0) - const token0USDValue = useUSDCValue(token0CurrencyAmount) || undefined - // TODO: compute the dependent value + const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0] + const independentAmount = tryParseCurrencyAmount(exactAmount, independentToken) + + const [, pool] = usePool(token0, token1, parseV3FeeTier(positionInfo.feeTier)) + const [, pair] = useV2Pair(token0, token1) + + const dependentAmount: CurrencyAmount | undefined = useMemo(() => { + // we wrap the currencies just to get the price in terms of the other token + const wrappedIndependentAmount = independentAmount?.wrapped + + if (positionInfo.restPosition.position.case === 'v2Pair') { + const [token0Wrapped, token1Wrapped] = [token0?.wrapped, token1?.wrapped] + + if (token0Wrapped && token1Wrapped && wrappedIndependentAmount && pair) { + const dependentTokenAmount = + exactField === PositionField.TOKEN0 + ? pair.priceOf(token0Wrapped).quote(wrappedIndependentAmount) + : pair.priceOf(token1Wrapped).quote(wrappedIndependentAmount) + return dependentToken?.isNative + ? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) + : dependentTokenAmount + } + return undefined + } + + if (positionInfo.restPosition.position.case === 'v3Position') { + const position: PoolPosition = positionInfo.restPosition.position.value + const { tickLower: tickLowerStr, tickUpper: tickUpperStr } = position + const tickLower = parseInt(tickLowerStr) + const tickUpper = parseInt(tickUpperStr) + + if ( + independentAmount && + wrappedIndependentAmount && + typeof tickLower === 'number' && + typeof tickUpper === 'number' && + pool + ) { + const position: Position | undefined = wrappedIndependentAmount.currency.equals(pool.token0) + ? Position.fromAmount0({ + pool, + tickLower, + tickUpper, + amount0: independentAmount.quotient, + useFullPrecision: true, // we want full precision for the theoretical position + }) + : Position.fromAmount1({ + pool, + tickLower, + tickUpper, + amount1: independentAmount.quotient, + }) + + const dependentTokenAmount = wrappedIndependentAmount.currency.equals(pool.token0) + ? position.amount1 + : position.amount0 + return dependentToken && CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) + } + + return undefined + } + + if (positionInfo.restPosition.position.case === 'v4Position') { + // TODO: calculate for v4 + return undefined + } + + return undefined + }, [ + dependentToken, + independentAmount, + pool, + positionInfo.restPosition.position, + exactField, + pair, + token0.wrapped, + token1.wrapped, + ]) + + const independentTokenUSDValue = useUSDCValue(independentAmount) || undefined + const dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined + const dependentField = exactField === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0 return { - formattedAmounts: { [Field.TOKEN0]: exactAmount }, - currencyBalances: { [Field.TOKEN0]: token0Balance, [Field.TOKEN1]: token1Balance }, - currencyAmounts: { [Field.TOKEN0]: token0CurrencyAmount }, - currencyAmountsUSDValue: { [Field.TOKEN0]: token0USDValue }, + currencyBalances: { [PositionField.TOKEN0]: token0Balance, [PositionField.TOKEN1]: token1Balance }, + formattedAmounts: { [exactField]: exactAmount, [dependentField]: dependentAmount?.toExact() }, + currencyAmounts: { [exactField]: independentAmount, [dependentField]: dependentAmount }, + currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue }, } } diff --git a/apps/web/src/components/swap/SwapHeader.test.tsx b/apps/web/src/components/swap/SwapHeader.test.tsx index 956593df5a0..2a7d8eb2bc1 100644 --- a/apps/web/src/components/swap/SwapHeader.test.tsx +++ b/apps/web/src/components/swap/SwapHeader.test.tsx @@ -1,9 +1,9 @@ import SwapHeader from 'components/swap/SwapHeader' -import { Field } from 'components/swap/constants' import { Dispatch, PropsWithChildren, SetStateAction } from 'react' import { CurrencyState, EMPTY_DERIVED_SWAP_INFO, SwapAndLimitContext, SwapContext } from 'state/swap/types' import { act, render, screen } from 'test-utils/render' import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' interface WrapperProps { @@ -37,7 +37,7 @@ function Wrapper(props: PropsWithChildren) { derivedSwapInfo: EMPTY_DERIVED_SWAP_INFO, setSwapState: jest.fn(), swapState: { - independentField: Field.INPUT, + independentField: CurrencyField.INPUT, typedValue: '', }, }} diff --git a/apps/web/src/components/swap/SwapLineItem.tsx b/apps/web/src/components/swap/SwapLineItem.tsx index 3f8a44e2241..3691bef360c 100644 --- a/apps/web/src/components/swap/SwapLineItem.tsx +++ b/apps/web/src/components/swap/SwapLineItem.tsx @@ -130,6 +130,7 @@ function FeeRow({ trade: { swapFee, outputAmount } }: { trade: SubmittableTrade return <>{formatNumber({ input: outputFeeFiatValue, type: NumberType.FiatGasPrice })} } +// eslint-disable-next-line consistent-return function useLineItem(props: SwapLineItemProps): LineItemData | undefined { const { trade, syncing, allowedSlippage, type, priceImpact } = props const { formatPercent } = useFormatter() @@ -157,7 +158,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { } case SwapLineItemType.NETWORK_COST: if (!SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)) { - return + return undefined } return { Label: () => , @@ -172,7 +173,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { case SwapLineItemType.PRICE_IMPACT: // Hides price impact row if the current trade is UniswapX or we're expecting a preview trade to result in UniswapX if (isUniswapX || !priceImpact || (isPreview && isUniswapXTradeType(lastSubmittableFillType))) { - return + return undefined } return { Label: () => , @@ -205,7 +206,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { } case SwapLineItemType.MAXIMUM_INPUT: if (trade.tradeType === TradeType.EXACT_INPUT) { - return + return undefined } return { Label: () => , @@ -242,7 +243,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { return getFOTLineItem(props) case SwapLineItemType.EXPIRY: if (!isLimitTrade(trade)) { - return + return undefined } return { Label: () => , @@ -256,7 +257,7 @@ function getFOTLineItem({ type, trade }: SwapLineItemProps): LineItemData | unde const currency = isInput ? trade.inputAmount.currency : trade.outputAmount.currency const tax = isInput ? trade.inputTax : trade.outputTax if (tax.equalTo(0)) { - return + return undefined } const tokenSymbol = currency.symbol ?? currency.name diff --git a/apps/web/src/components/swap/SwapModalHeaderAmount.tsx b/apps/web/src/components/swap/SwapModalHeaderAmount.tsx index c9cebdd241c..c0fa4e1c30b 100644 --- a/apps/web/src/components/swap/SwapModalHeaderAmount.tsx +++ b/apps/web/src/components/swap/SwapModalHeaderAmount.tsx @@ -3,13 +3,13 @@ import CurrencyLogo from 'components/Logo/CurrencyLogo' import { MouseoverTooltip } from 'components/Tooltip' import Column from 'components/deprecated/Column' import Row from 'components/deprecated/Row' -import { Field } from 'components/swap/constants' import { useWindowSize } from 'hooks/screenSize/useWindowSize' import styled from 'lib/styled-components' import { PropsWithChildren, ReactNode } from 'react' import { TextProps } from 'rebass' import { BREAKPOINTS } from 'theme' import { ThemedText } from 'theme/components' +import { CurrencyField } from 'uniswap/src/types/currency' import { NumberType, useFormatter } from 'utils/formatNumbers' const Label = styled(ThemedText.BodySmall)<{ cursor?: string }>` @@ -30,7 +30,7 @@ const ResponsiveHeadline = ({ children, ...textProps }: PropsWithChildren diff --git a/apps/web/src/components/swap/SwapPreview.test.tsx b/apps/web/src/components/swap/SwapPreview.test.tsx index fc524d5688d..6f9627dc97d 100644 --- a/apps/web/src/components/swap/SwapPreview.test.tsx +++ b/apps/web/src/components/swap/SwapPreview.test.tsx @@ -20,8 +20,8 @@ describe('SwapPreview.tsx', () => { ) expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ABC`) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 DEF`) + expect(screen.getByTestId('input-amount')).toHaveTextContent(`<0.00001 ABC`) + expect(screen.getByTestId('output-amount')).toHaveTextContent(`<0.00001 DEF`) }) it('renders ETH input token for an ETH input UniswapX swap', () => { @@ -34,8 +34,8 @@ describe('SwapPreview.tsx', () => { ) expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ETH`) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 DEF`) + expect(screen.getByTestId('input-amount')).toHaveTextContent(`<0.00001 ETH`) + expect(screen.getByTestId('output-amount')).toHaveTextContent(`<0.00001 DEF`) }) it('renders ETH input token for an ETH input UniswapX v2 swap', () => { @@ -48,8 +48,8 @@ describe('SwapPreview.tsx', () => { ) expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ETH`) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 DEF`) + expect(screen.getByTestId('input-amount')).toHaveTextContent(`<0.00001 ETH`) + expect(screen.getByTestId('output-amount')).toHaveTextContent(`<0.00001 DEF`) }) it('test trade exact output, no recipient', () => { @@ -59,8 +59,8 @@ describe('SwapPreview.tsx', () => { expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Input is estimated. You will sell at most/i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ABC`) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 GHI`) + expect(screen.getByTestId('input-amount')).toHaveTextContent(`<0.00001 ABC`) + expect(screen.getByTestId('output-amount')).toHaveTextContent(`<0.00001 GHI`) }) it('renders preview trades with loading states', () => { diff --git a/apps/web/src/components/swap/SwapPreview.tsx b/apps/web/src/components/swap/SwapPreview.tsx index 21073e466b8..6ff18513e0f 100644 --- a/apps/web/src/components/swap/SwapPreview.tsx +++ b/apps/web/src/components/swap/SwapPreview.tsx @@ -1,13 +1,13 @@ import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import Column, { AutoColumn } from 'components/deprecated/Column' import { SwapModalHeaderAmount } from 'components/swap/SwapModalHeaderAmount' -import { Field } from 'components/swap/constants' import { useUSDPrice } from 'hooks/useUSDPrice' import styled from 'lib/styled-components' import { InterfaceTrade } from 'state/routing/types' import { isPreviewTrade } from 'state/routing/utils' import { ThemedText } from 'theme/components' import { Trans } from 'uniswap/src/i18n' +import { CurrencyField } from 'uniswap/src/types/currency' const HeaderContainer = styled(AutoColumn)` margin-top: 0px; @@ -29,7 +29,7 @@ export function SwapPreview({ } amount={trade.inputAmount} currency={inputCurrency ?? trade.inputAmount.currency} @@ -37,7 +37,7 @@ export function SwapPreview({ isLoading={isPreviewTrade(trade) && trade.tradeType === TradeType.EXACT_OUTPUT} /> } amount={trade.outputAmount} currency={trade.outputAmount.currency} diff --git a/apps/web/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/apps/web/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index e905125f95b..33b7d62604f 100644 --- a/apps/web/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/apps/web/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -1780,7 +1780,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` } .c38 img { - width: 10px; + width: 9px; height: 20px; object-fit: cover; } @@ -2779,7 +2779,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` } .c38 img { - width: 10px; + width: 9px; height: 20px; object-fit: cover; } @@ -3778,7 +3778,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` } .c38 img { - width: 10px; + width: 9px; height: 20px; object-fit: cover; } @@ -4818,7 +4818,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` } .c38 img { - width: 10px; + width: 9px; height: 20px; object-fit: cover; } @@ -5871,7 +5871,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` } .c38 img { - width: 10px; + width: 9px; height: 20px; object-fit: cover; } diff --git a/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap b/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap index c44ebc10b4f..52834c5e8b4 100644 --- a/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap +++ b/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap @@ -192,7 +192,7 @@ exports[`SwapPreview.tsx matches base snapshot, test trade exact input 1`] = ` >
<0.00001 ABC
@@ -255,7 +255,7 @@ exports[`SwapPreview.tsx matches base snapshot, test trade exact input 1`] = ` >
<0.00001 DEF
@@ -491,7 +491,7 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX swap >
<0.00001 ETH
@@ -554,7 +554,7 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX swap >
<0.00001 DEF
@@ -790,7 +790,7 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX v2 sw >
<0.00001 ETH
@@ -853,7 +853,7 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX v2 sw >
<0.00001 DEF
@@ -1089,7 +1089,7 @@ exports[`SwapPreview.tsx renders preview trades with loading states 1`] = ` >
<0.00001 DEF
@@ -1152,7 +1152,7 @@ exports[`SwapPreview.tsx renders preview trades with loading states 1`] = ` >
<0.00001 DEF
@@ -1388,7 +1388,7 @@ exports[`SwapPreview.tsx test trade exact output, no recipient 1`] = ` >
<0.00001 ABC
@@ -1451,7 +1451,7 @@ exports[`SwapPreview.tsx test trade exact output, no recipient 1`] = ` >
<0.00001 GHI
diff --git a/apps/web/src/components/swap/constants.ts b/apps/web/src/components/swap/constants.ts index 8a5c722cc6f..1d70d7969ff 100644 --- a/apps/web/src/components/swap/constants.ts +++ b/apps/web/src/components/swap/constants.ts @@ -3,8 +3,3 @@ import { LDO, MNW, NMR, USDT as USDT_MAINNET } from 'uniswap/src/constants/token // List of tokens that require existing allowance to be reset before approving the new amount (mainnet only). // See the `approve` function here: https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7#code export const RESET_APPROVAL_TOKENS = [USDT_MAINNET, LDO, NMR, MNW] - -export enum Field { - INPUT = 'INPUT', - OUTPUT = 'OUTPUT', -} diff --git a/apps/web/src/constants/chains.ts b/apps/web/src/constants/chains.ts index 855b466d07a..b3318d2fe27 100644 --- a/apps/web/src/constants/chains.ts +++ b/apps/web/src/constants/chains.ts @@ -5,8 +5,9 @@ import { useCallback, useMemo } from 'react' import { useParams } from 'react-router-dom' import { GQL_MAINNET_CHAINS, UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { Chain as BackendChainId } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { ArbitrumXV2ExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' import { FeatureFlags } from 'uniswap/src/features/gating/flags' -import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useExperimentGroupName, useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { InterfaceChainId, UniverseChainId, UniverseChainInfo, WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' export const AVERAGE_L1_BLOCK_TIME = ms(`12s`) @@ -52,7 +53,7 @@ export function useIsSupportedChainIdCallback() { export function useSupportedChainId(chainId?: number): SupportedInterfaceChainId | undefined { const featureFlaggedChains = useFeatureFlaggedChainIds() if (!chainId || WEB_SUPPORTED_CHAIN_IDS.indexOf(chainId) === -1) { - return + return undefined } const chainDisabled = featureFlaggedChains[chainId as SupportedInterfaceChainId] === false @@ -179,8 +180,16 @@ export function getChainPriority(chainId: InterfaceChainId): number { return Infinity } -export function isUniswapXSupportedChain(chainId?: number) { - return chainId === UniverseChainId.Mainnet +export function useIsUniswapXSupportedChain(chainId?: number) { + const xv2ArbitrumEnabled = + useExperimentGroupName(Experiments.ArbitrumXV2OpenOrders) === ArbitrumXV2ExperimentGroup.Test + const isPriorityOrdersEnabled = useFeatureFlag(FeatureFlags.UniswapXPriorityOrders) + + return ( + chainId === UniverseChainId.Mainnet || + (xv2ArbitrumEnabled && chainId === UniverseChainId.ArbitrumOne) || + (isPriorityOrdersEnabled && chainId === UniverseChainId.Base) // UniswapX priority orders are only available on Base for now + ) } export function isStablecoin(currency?: Currency): boolean { diff --git a/apps/web/src/constants/routing.test.ts b/apps/web/src/constants/routing.test.ts index cd4bae70530..44d686c6142 100644 --- a/apps/web/src/constants/routing.test.ts +++ b/apps/web/src/constants/routing.test.ts @@ -17,7 +17,7 @@ describe('Routing', () => { }) it('contains all coins for polygon', () => { const symbols = COMMON_BASES[UniverseChainId.Polygon].map((coin) => coin.currency.symbol) - expect(symbols).toEqual(['MATIC', 'WETH', 'USDC', 'DAI', 'USDT', 'WBTC']) + expect(symbols).toEqual(['POL', 'WETH', 'USDC', 'DAI', 'USDT', 'WBTC']) }) it('contains all coins for celo', () => { const symbols = COMMON_BASES[UniverseChainId.Celo].map((coin) => coin.currency.symbol) diff --git a/apps/web/src/graphql/data/ConversionRate.ts b/apps/web/src/graphql/data/ConversionRate.ts deleted file mode 100644 index 66e67cddaea..00000000000 --- a/apps/web/src/graphql/data/ConversionRate.ts +++ /dev/null @@ -1,19 +0,0 @@ -import ms from 'ms' -import { useConvertWebQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' -import { mapFiatCurrencyToServerCurrency } from 'uniswap/src/features/fiatCurrency/conversion' -import { getFetchPolicyForKey } from 'utils/getFetchPolicyForKey' - -// TODO(WALL-4578): converge conversion rate code to use the shared localization context -export function useLocalCurrencyConversionRate(localCurrency: FiatCurrency, skip?: boolean) { - const { data, loading } = useConvertWebQuery({ - variables: { toCurrency: mapFiatCurrencyToServerCurrency[localCurrency] }, - fetchPolicy: getFetchPolicyForKey(`convert-${localCurrency}`, ms('5m')), - skip, - }) - - return { - data: data?.convert?.value, - isLoading: loading, - } -} diff --git a/apps/web/src/graphql/data/apollo/AdaptiveRefetch.tsx b/apps/web/src/graphql/data/apollo/AdaptiveRefetch.tsx index 6ee88817ab8..ea387a82397 100644 --- a/apps/web/src/graphql/data/apollo/AdaptiveRefetch.tsx +++ b/apps/web/src/graphql/data/apollo/AdaptiveRefetch.tsx @@ -80,7 +80,7 @@ export function createAdaptiveRefetchContext() { // Subscribing/unsubscribing allows AdaptiveRefetchProvider to track whether components are currently using the query or not, impacting whether or not to re-fetch when stale. useEffect(() => { if (options?.cacheOnly === true) { - return + return undefined } return subscribe() }, [options?.cacheOnly, subscribe]) diff --git a/apps/web/src/graphql/data/apollo/AdaptiveTokenBalancesProvider.tsx b/apps/web/src/graphql/data/apollo/AdaptiveTokenBalancesProvider.tsx new file mode 100644 index 00000000000..5eeb6b1064f --- /dev/null +++ b/apps/web/src/graphql/data/apollo/AdaptiveTokenBalancesProvider.tsx @@ -0,0 +1,10 @@ +import { createAdaptiveRefetchContext } from 'graphql/data/apollo/AdaptiveRefetch' +import { PortfolioBalancesQueryResult } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' + +const { + Provider: AdaptiveTokenBalancesProvider, + useQuery: useTokenBalancesQuery, + PrefetchWrapper: PrefetchBalancesWrapper, +} = createAdaptiveRefetchContext() + +export { AdaptiveTokenBalancesProvider, PrefetchBalancesWrapper, useTokenBalancesQuery } diff --git a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx index b3aeaad2a7e..b4f02e14ec4 100644 --- a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx +++ b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx @@ -1,5 +1,5 @@ import { fireEvent, screen } from '@testing-library/react' -import { PrefetchBalancesWrapper, useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper, useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useAccount } from 'hooks/useAccount' import { mocked } from 'test-utils/mocked' import { render, renderHook } from 'test-utils/render' diff --git a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx index 7fb0b26aa46..985f22a63a3 100644 --- a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx +++ b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx @@ -1,31 +1,21 @@ import { usePendingActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks' -import { createAdaptiveRefetchContext } from 'graphql/data/apollo/AdaptiveRefetch' +import { AdaptiveTokenBalancesProvider } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useAssetActivitySubscription } from 'graphql/data/apollo/AssetActivityProvider' import { useAccount } from 'hooks/useAccount' -import { PropsWithChildren, useCallback, useEffect, useMemo } from 'react' +import { PropsWithChildren, useCallback, useMemo } from 'react' import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' -import { useTotalBalancesUsdPerChain } from 'uniswap/src/data/balances/utils' import { OnAssetActivitySubscription, - PortfolioBalancesQueryResult, SwapOrderStatus, usePortfolioBalancesLazyQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' -import { UniswapEventName } from 'uniswap/src/features/telemetry/constants' -import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { InterfaceChainId } from 'uniswap/src/types/chains' import { SUBSCRIPTION_CHAINIDS } from 'utilities/src/apollo/constants' import { usePrevious } from 'utilities/src/react/hooks' -const { - Provider: AdaptiveTokenBalancesProvider, - useQuery: useTokenBalancesQuery, - PrefetchWrapper: PrefetchBalancesWrapper, -} = createAdaptiveRefetchContext() - /** Returns whether an update may affect token balances. */ function mayAffectTokenBalances(data?: OnAssetActivitySubscription) { // Special case: non-filled order status updates do not affect balances. @@ -122,42 +112,3 @@ export function TokenBalancesProvider({ children }: PropsWithChildren) { ) } - -/** - * Retrieves cached token balances, avoiding new fetches to reduce backend load. - * Analytics should use balances from transaction flows instead of initiating fetches at pageload. - */ -export function useTotalBalancesUsdForAnalytics(): number | undefined { - return useTokenBalancesQuery({ cacheOnly: true }).data?.portfolios?.[0]?.tokensTotalDenominatedValue?.value -} - -export function useReportTotalBalancesUsdForAnalytics() { - const account = useAccount() - const portfolioBalanceUsd = useTotalBalancesUsdForAnalytics() - const totalBalancesUsdPerChain = useTotalBalancesUsdPerChain(useTokenBalancesQuery({ cacheOnly: true })) - - const sendBalancesReport = useCallback(async () => { - if (!portfolioBalanceUsd || !totalBalancesUsdPerChain || !account.address) { - return - } - - sendAnalyticsEvent(UniswapEventName.BalancesReport, { - total_balances_usd: portfolioBalanceUsd, - wallets: [account.address], - balances: [portfolioBalanceUsd], - }) - - sendAnalyticsEvent(UniswapEventName.BalancesReportPerChain, { - total_balances_usd_per_chain: totalBalancesUsdPerChain, - wallet: account.address, - }) - }, [portfolioBalanceUsd, totalBalancesUsdPerChain, account.address]) - - useEffect(() => { - if (portfolioBalanceUsd !== undefined && totalBalancesUsdPerChain !== undefined) { - sendBalancesReport() - } - }, [portfolioBalanceUsd, totalBalancesUsdPerChain, sendBalancesReport]) -} - -export { PrefetchBalancesWrapper, useTokenBalancesQuery } diff --git a/apps/web/src/graphql/data/apollo/useReportTotalBalancesUsdForAnalytics.ts b/apps/web/src/graphql/data/apollo/useReportTotalBalancesUsdForAnalytics.ts new file mode 100644 index 00000000000..6dd098a6f2c --- /dev/null +++ b/apps/web/src/graphql/data/apollo/useReportTotalBalancesUsdForAnalytics.ts @@ -0,0 +1,36 @@ +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' +import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics' +import { useAccount } from 'hooks/useAccount' +import { useCallback, useEffect } from 'react' +import { useTotalBalancesUsdPerChain } from 'uniswap/src/data/balances/utils' +import { UniswapEventName } from 'uniswap/src/features/telemetry/constants' +import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' + +export function useReportTotalBalancesUsdForAnalytics() { + const account = useAccount() + const portfolioBalanceUsd = useTotalBalancesUsdForAnalytics() + const totalBalancesUsdPerChain = useTotalBalancesUsdPerChain(useTokenBalancesQuery({ cacheOnly: true })) + + const sendBalancesReport = useCallback(async () => { + if (!portfolioBalanceUsd || !totalBalancesUsdPerChain || !account.address) { + return + } + + sendAnalyticsEvent(UniswapEventName.BalancesReport, { + total_balances_usd: portfolioBalanceUsd, + wallets: [account.address], + balances: [portfolioBalanceUsd], + }) + + sendAnalyticsEvent(UniswapEventName.BalancesReportPerChain, { + total_balances_usd_per_chain: totalBalancesUsdPerChain, + wallet: account.address, + }) + }, [portfolioBalanceUsd, totalBalancesUsdPerChain, account.address]) + + useEffect(() => { + if (portfolioBalanceUsd !== undefined && totalBalancesUsdPerChain !== undefined) { + sendBalancesReport() + } + }, [portfolioBalanceUsd, totalBalancesUsdPerChain, sendBalancesReport]) +} diff --git a/apps/web/src/graphql/data/apollo/useTotalBalancesUsdForAnalytics.ts b/apps/web/src/graphql/data/apollo/useTotalBalancesUsdForAnalytics.ts new file mode 100644 index 00000000000..d9409036e32 --- /dev/null +++ b/apps/web/src/graphql/data/apollo/useTotalBalancesUsdForAnalytics.ts @@ -0,0 +1,9 @@ +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' + +/** + * Retrieves cached token balances, avoiding new fetches to reduce backend load. + * Analytics should use balances from transaction flows instead of initiating fetches at pageload. + */ +export function useTotalBalancesUsdForAnalytics(): number | undefined { + return useTokenBalancesQuery({ cacheOnly: true }).data?.portfolios?.[0]?.tokensTotalDenominatedValue?.value +} diff --git a/apps/web/src/graphql/data/types.ts b/apps/web/src/graphql/data/types.ts index 7d44a6e9c00..388a32f6bf5 100644 --- a/apps/web/src/graphql/data/types.ts +++ b/apps/web/src/graphql/data/types.ts @@ -40,7 +40,7 @@ export function gqlTokenToCurrencyInfo(token?: GqlToken): CurrencyInfo | undefin export function meldSupportedCurrencyToCurrencyInfo(forCurrency: FORSupportedToken): CurrencyInfo | undefined { if (!isSupportedChainId(Number(forCurrency.chainId))) { - return + return undefined } const supportedChainId = Number(forCurrency.chainId) as SupportedInterfaceChainId @@ -67,7 +67,7 @@ export function meldSupportedCurrencyToCurrencyInfo(forCurrency: FORSupportedTok const currency = fiatOnRampToCurrency(forCurrency) if (!currency) { - return + return undefined } return { currency, diff --git a/apps/web/src/graphql/data/util.tsx b/apps/web/src/graphql/data/util.tsx index 32f32470621..82f6823178b 100644 --- a/apps/web/src/graphql/data/util.tsx +++ b/apps/web/src/graphql/data/util.tsx @@ -65,6 +65,7 @@ export enum TimePeriod { YEAR = 'Y', } +// eslint-disable-next-line consistent-return export function toHistoryDuration(timePeriod: TimePeriod): HistoryDuration { switch (timePeriod) { case TimePeriod.HOUR: @@ -111,14 +112,14 @@ export function gqlToCurrency(token: DeepPartial): Currenc token.address, token.decimals ?? 18, token.symbol ?? undefined, - token.project?.name ?? token.name ?? undefined, + token.name ?? token.project?.name ?? undefined, ) } } export function fiatOnRampToCurrency(forCurrency: FORSupportedToken): Currency | undefined { if (!isSupportedChainId(Number(forCurrency.chainId))) { - return + return undefined } const supportedChainId = Number(forCurrency.chainId) as SupportedInterfaceChainId diff --git a/apps/web/src/hooks/useConfirmModalState.ts b/apps/web/src/hooks/useConfirmModalState.ts index 9e0355f2de4..d23c61985a6 100644 --- a/apps/web/src/hooks/useConfirmModalState.ts +++ b/apps/web/src/hooks/useConfirmModalState.ts @@ -2,7 +2,7 @@ import { InterfaceEventName } from '@uniswap/analytics-events' import { Currency, Percent } from '@uniswap/sdk-core' import { ConfirmModalState } from 'components/ConfirmSwapModal' import { PendingModalError } from 'components/ConfirmSwapModal/Error' -import { Field, RESET_APPROVAL_TOKENS } from 'components/swap/constants' +import { RESET_APPROVAL_TOKENS } from 'components/swap/constants' import { useAccount } from 'hooks/useAccount' import { useMaxAmountIn } from 'hooks/useMaxAmountIn' import { Allowance, AllowanceState } from 'hooks/usePermit2Allowance' @@ -18,6 +18,7 @@ import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { useIsTransactionConfirmed } from 'state/transactions/hooks' import invariant from 'tiny-invariant' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { CurrencyField } from 'uniswap/src/types/currency' import { logger } from 'utilities/src/logger/logger' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { NumberType, useFormatter } from 'utils/formatNumbers' @@ -46,7 +47,7 @@ export function useConfirmModalState({ allowedSlippage: Percent onSwap: () => void allowance: Allowance - onCurrencySelection: (field: Field, currency: Currency) => void + onCurrencySelection: (field: CurrencyField, currency: Currency) => void }) { const [confirmModalState, setConfirmModalState] = useState(ConfirmModalState.REVIEWING) const [approvalError, setApprovalError] = useState() @@ -121,7 +122,7 @@ export function useConfirmModalState({ setWrapTxHash(wrapTxHash) // After the wrap has succeeded, reset the input currency to be WETH // because the trade will be on WETH -> token - onCurrencySelection(Field.INPUT, trade.inputAmount.currency) + onCurrencySelection(CurrencyField.INPUT, trade.inputAmount.currency) sendAnalyticsEvent(InterfaceEventName.WRAP_TOKEN_TXN_SUBMITTED, { chain_id: chainId, token_symbol: maximumAmountIn?.currency.symbol, diff --git a/apps/web/src/hooks/useEthersProvider.ts b/apps/web/src/hooks/useEthersProvider.ts index 91a1b793f78..0fe25fa89ea 100644 --- a/apps/web/src/hooks/useEthersProvider.ts +++ b/apps/web/src/hooks/useEthersProvider.ts @@ -7,7 +7,7 @@ import { useClient, useConnectorClient } from 'wagmi' const providers = new WeakMap() -function clientToProvider(client?: Client, chainId?: number) { +export function clientToProvider(client?: Client, chainId?: number) { if (!client) { return undefined } diff --git a/apps/web/src/hooks/useKeyPress.ts b/apps/web/src/hooks/useKeyPress.ts index 33badc6078f..44c91d4fc78 100644 --- a/apps/web/src/hooks/useKeyPress.ts +++ b/apps/web/src/hooks/useKeyPress.ts @@ -20,7 +20,7 @@ export const useKeyPress = ({ }) => { useEffect(() => { if (!keys || disabled) { - return + return undefined } const onKeyPress = (event: any) => { const wasAnyKeyPressed = keys.some((key) => event.key === key) diff --git a/apps/web/src/hooks/useSelectChain.ts b/apps/web/src/hooks/useSelectChain.ts index 96b85147abf..3f97f2ad9b0 100644 --- a/apps/web/src/hooks/useSelectChain.ts +++ b/apps/web/src/hooks/useSelectChain.ts @@ -1,13 +1,13 @@ import { useSwitchChain } from 'hooks/useSwitchChain' import { useCallback } from 'react' +import { useDispatch } from 'react-redux' import { PopupType, addPopup, removePopup } from 'state/application/reducer' -import { useAppDispatch } from 'state/hooks' import { InterfaceChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { UserRejectedRequestError } from 'viem' export default function useSelectChain() { - const dispatch = useAppDispatch() + const dispatch = useDispatch() const switchChain = useSwitchChain() return useCallback( diff --git a/apps/web/src/hooks/useSwapTaxes.ts b/apps/web/src/hooks/useSwapTaxes.ts index 2033271abe5..b1121c13ae8 100644 --- a/apps/web/src/hooks/useSwapTaxes.ts +++ b/apps/web/src/hooks/useSwapTaxes.ts @@ -94,6 +94,7 @@ async function getSwapTaxes( return { inputTax, outputTax } } +// Use the buyFeeBps/sellFeeBps fields from Token GQL query where possible instead of this hook export function useSwapTaxes(inputTokenAddress?: string, outputTokenAddress?: string, tokenChainId?: InterfaceChainId) { const account = useAccount() const chainId = tokenChainId ?? account.chainId diff --git a/apps/web/src/hooks/useSwitchChain.ts b/apps/web/src/hooks/useSwitchChain.ts index 4bb9b35bcd4..278a489bbeb 100644 --- a/apps/web/src/hooks/useSwitchChain.ts +++ b/apps/web/src/hooks/useSwitchChain.ts @@ -1,14 +1,14 @@ import { useIsSupportedChainIdCallback } from 'constants/chains' import { useAccount } from 'hooks/useAccount' import { useCallback } from 'react' -import { useAppDispatch } from 'state/hooks' +import { useDispatch } from 'react-redux' import { endSwitchingChain, startSwitchingChain } from 'state/wallets/reducer' import { trace } from 'tracing/trace' import { InterfaceChainId } from 'uniswap/src/types/chains' import { useSwitchChain as useSwitchChainWagmi } from 'wagmi' export function useSwitchChain() { - const dispatch = useAppDispatch() + const dispatch = useDispatch() const isSupportedChainCallback = useIsSupportedChainIdCallback() const { switchChain } = useSwitchChainWagmi() const account = useAccount() @@ -21,7 +21,7 @@ export function useSwitchChain() { } if (account.chainId === chainId) { // some wallets (e.g. SafeWallet) only support single-chain & will throw error on `switchChain` even if already on the correct chain - return + return undefined } return trace( { name: 'Switch chain', op: 'wallet.switch_chain' }, diff --git a/apps/web/src/hooks/useTokenBalances.test.ts b/apps/web/src/hooks/useTokenBalances.test.ts index 39153a60194..5320afef5c9 100644 --- a/apps/web/src/hooks/useTokenBalances.test.ts +++ b/apps/web/src/hooks/useTokenBalances.test.ts @@ -1,6 +1,6 @@ import { useWeb3React } from '@web3-react/core' import { NATIVE_CHAIN_ID } from 'constants/tokens' -import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useTokenBalances } from 'hooks/useTokenBalances' import { mocked } from 'test-utils/mocked' import { renderHook } from 'test-utils/render' @@ -13,8 +13,8 @@ jest.mock('@web3-react/core', () => ({ useWeb3React: jest.fn(() => ({ account: '0x123', chainId: 1 })), })) -jest.mock('graphql/data/apollo/TokenBalancesProvider', () => ({ - ...jest.requireActual('graphql/data/apollo/TokenBalancesProvider'), +jest.mock('graphql/data/apollo/AdaptiveTokenBalancesProvider', () => ({ + ...jest.requireActual('graphql/data/apollo/AdaptiveTokenBalancesProvider'), useTokenBalancesQuery: jest.fn(() => ({ data: {}, loading: false })), })) diff --git a/apps/web/src/hooks/useTokenBalances.ts b/apps/web/src/hooks/useTokenBalances.ts index 758ce011377..bee4eed2761 100644 --- a/apps/web/src/hooks/useTokenBalances.ts +++ b/apps/web/src/hooks/useTokenBalances.ts @@ -1,4 +1,4 @@ -import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { PortfolioBalance } from 'graphql/data/portfolios' import { useAccount } from 'hooks/useAccount' import { TokenBalances } from 'lib/hooks/useTokenList/sorting' diff --git a/apps/web/src/hooks/useTransactionGasFee.ts b/apps/web/src/hooks/useTransactionGasFee.ts index 7bab1638618..18189342a09 100644 --- a/apps/web/src/hooks/useTransactionGasFee.ts +++ b/apps/web/src/hooks/useTransactionGasFee.ts @@ -108,7 +108,7 @@ const isErrorResponse = (res: Response, gasFee: GasFeeResponse): gasFee is GasFe function useGasFeeQuery(tx?: TransactionRequest, skip: boolean = !tx) { const gasFeeFetcher = useCallback(async () => { if (skip) { - return + return undefined } const res = await fetch(`${UNISWAP_API_URL}/v1/gas-fee`, { @@ -119,7 +119,7 @@ function useGasFeeQuery(tx?: TransactionRequest, skip: boolean = !tx) { const body = (await res.json()) as GasFeeResponse if (isErrorResponse(res, body)) { - return + return undefined } return body diff --git a/apps/web/src/hooks/useUSDTokenUpdater.ts b/apps/web/src/hooks/useUSDTokenUpdater.ts index d72b8a15ae7..afd86b8ad1f 100644 --- a/apps/web/src/hooks/useUSDTokenUpdater.ts +++ b/apps/web/src/hooks/useUSDTokenUpdater.ts @@ -19,7 +19,7 @@ export function useUSDTokenUpdater( } { const { price, state } = useStablecoinPrice(exactCurrency) const { convertToFiatAmount, formatCurrencyAmount } = useFormatter() - const conversionRate = convertToFiatAmount().amount + const conversionRate = convertToFiatAmount(1).amount const supportedChainId = useSupportedChainId(exactCurrency?.chainId) return useMemo(() => { diff --git a/apps/web/src/hooks/useUniswapXSwapCallback.ts b/apps/web/src/hooks/useUniswapXSwapCallback.ts index 22ecab6ce4b..39523146b20 100644 --- a/apps/web/src/hooks/useUniswapXSwapCallback.ts +++ b/apps/web/src/hooks/useUniswapXSwapCallback.ts @@ -3,8 +3,15 @@ import { BigNumber } from '@ethersproject/bignumber' import { CustomUserProperties, SwapEventName } from '@uniswap/analytics-events' import { PermitTransferFrom } from '@uniswap/permit2-sdk' import { Percent } from '@uniswap/sdk-core' -import { DutchOrder, DutchOrderBuilder, UnsignedV2DutchOrder, V2DutchOrderBuilder } from '@uniswap/uniswapx-sdk' -import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/TokenBalancesProvider' +import { + DutchOrder, + DutchOrderBuilder, + PriorityOrderBuilder, + UnsignedPriorityOrder, + UnsignedV2DutchOrder, + V2DutchOrderBuilder, +} from '@uniswap/uniswapx-sdk' +import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics' import { useAccount } from 'hooks/useAccount' import { useEthersWeb3Provider } from 'hooks/useEthersProvider' import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' @@ -13,6 +20,7 @@ import { DutchOrderTrade, LimitOrderTrade, OffchainOrderType, + PriorityOrderTrade, TradeFillType, V2DutchOrderTrade, } from 'state/routing/types' @@ -77,7 +85,7 @@ export function useUniswapXSwapCallback({ allowedSlippage, fiatValues, }: { - trade?: DutchOrderTrade | V2DutchOrderTrade | LimitOrderTrade + trade?: DutchOrderTrade | V2DutchOrderTrade | LimitOrderTrade | PriorityOrderTrade fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number } allowedSlippage: Percent }) { @@ -131,7 +139,7 @@ export function useUniswapXSwapCallback({ let domain: TypedDataDomain let types: Record let values: PermitTransferFrom - let updatedOrder: DutchOrder | UnsignedV2DutchOrder + let updatedOrder: DutchOrder | UnsignedV2DutchOrder | UnsignedPriorityOrder if (trade instanceof V2DutchOrderTrade) { deadline = now + trade.deadlineBufferSecs @@ -144,6 +152,17 @@ export function useUniswapXSwapCallback({ .nonce(updatedNonce ?? order.info.nonce) .buildPartial() ;({ domain, types, values } = updatedOrder.permitData()) + } else if (trade instanceof PriorityOrderTrade) { + deadline = now + trade.deadlineBufferSecs + + const order = trade.order + updatedOrder = PriorityOrderBuilder.fromOrder(order) + .deadline(deadline) + .nonFeeRecipient(account.address, trade.swapFee?.recipient) + // if fetching the nonce fails for any reason, default to existing nonce from the Swap quote. + .nonce(updatedNonce ?? order.info.nonce) + .buildPartial() + ;({ domain, types, values } = updatedOrder.permitData()) } else { const startTime = now + trade.startTimeBufferSecs const endTime = startTime + trade.auctionPeriodSecs @@ -163,7 +182,6 @@ export function useUniswapXSwapCallback({ trace.setData('startTime', startTime) trace.setData('endTime', endTime) } - trace.setData('deadline', deadline) const signature = await trace.child({ name: 'Sign', op: 'wallet.sign' }, async (walletTrace) => { @@ -237,6 +255,7 @@ export function useUniswapXSwapCallback({ signature, chainId: updatedOrder.chainId, quoteId: trade.quoteId, + requestId: trade.requestId, } } diff --git a/apps/web/src/hooks/useUniversalRouter.ts b/apps/web/src/hooks/useUniversalRouter.ts index f5b5afab550..95a141510c0 100644 --- a/apps/web/src/hooks/useUniversalRouter.ts +++ b/apps/web/src/hooks/useUniversalRouter.ts @@ -4,7 +4,7 @@ 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 { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics' import { useAccount } from 'hooks/useAccount' import { useEthersWeb3Provider } from 'hooks/useEthersProvider' import { PermitSignature } from 'hooks/usePermitAllowance' diff --git a/apps/web/src/hooks/useUnmountingAnimation.ts b/apps/web/src/hooks/useUnmountingAnimation.ts index 325afb2b2ef..6de835a3194 100644 --- a/apps/web/src/hooks/useUnmountingAnimation.ts +++ b/apps/web/src/hooks/useUnmountingAnimation.ts @@ -40,7 +40,7 @@ export function useUnmountingAnimation( // If we can't remove the child or skipping is requested, stop here. if (!(parent && removeChild) || skip) { - return + return undefined } // Override the parent's removeChild function to add our animation logic diff --git a/apps/web/src/lib/hooks/routing/useRoutingAPIArguments.ts b/apps/web/src/lib/hooks/routing/useRoutingAPIArguments.ts index 1b2232f4cf0..4fa05656f22 100644 --- a/apps/web/src/lib/hooks/routing/useRoutingAPIArguments.ts +++ b/apps/web/src/lib/hooks/routing/useRoutingAPIArguments.ts @@ -1,19 +1,19 @@ import { SkipToken, skipToken } from '@reduxjs/toolkit/query/react' import { Protocol } from '@uniswap/router-sdk' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' +import { useIsUniswapXSupportedChain } from 'constants/chains' import { useMemo } from 'react' import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { currencyAddressForSwapQuote } from 'state/routing/utils' -import { ArbitrumXV2OpenOrderProperties, Experiments } from 'uniswap/src/features/gating/experiments' +import { + ArbitrumXV2ExperimentGroup, + ArbitrumXV2OpenOrderProperties, + Experiments, +} from 'uniswap/src/features/gating/experiments' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useExperimentGroupName, useExperimentValue, useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { UniverseChainId } from 'uniswap/src/types/chains' -enum ArbitrumXV2ExperimentGroup { - Test = 'Test', - Control = 'Control', -} - /** * Returns query arguments for the Routing API query or undefined if the * query should be skipped. Input arguments do not need to be memoized, as they will @@ -37,6 +37,7 @@ export function useRoutingAPIArguments({ protocolPreferences?: Protocol[] }): GetQuoteArgs | SkipToken { const uniswapXForceSyntheticQuotes = useFeatureFlag(FeatureFlags.UniswapXSyntheticQuote) + const isPriorityOrdersEnabled = useFeatureFlag(FeatureFlags.UniswapXPriorityOrders) const isXv2 = useFeatureFlag(FeatureFlags.UniswapXv2) const xv2ArbitrumEnabled = useExperimentGroupName(Experiments.ArbitrumXV2OpenOrders) === ArbitrumXV2ExperimentGroup.Test @@ -64,6 +65,10 @@ export function useRoutingAPIArguments({ // Don't enable fee logic if this is a quote for pricing const sendPortionEnabled = routerPreference !== INTERNAL_ROUTER_PREFERENCE_PRICE + const chainId = tokenIn?.chainId + const isUniswapXSupportedChain = useIsUniswapXSupportedChain(chainId) + const isPriorityOrder = isPriorityOrdersEnabled && routerPreference === RouterPreference.X && isUniswapXSupportedChain + return useMemo( () => !tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut) || tokenIn.wrapped.equals(tokenOut.wrapped) @@ -91,6 +96,8 @@ export function useRoutingAPIArguments({ forceOpenOrders, deadlineBufferSecs, arbitrumXV2SlippageTolerance, + isPriorityOrder, + isUniswapXSupportedChain, }, [ tokenIn, @@ -108,6 +115,8 @@ export function useRoutingAPIArguments({ forceOpenOrders, deadlineBufferSecs, arbitrumXV2SlippageTolerance, + isPriorityOrder, + isUniswapXSupportedChain, ], ) } diff --git a/apps/web/src/lib/hooks/useBlockNumber.tsx b/apps/web/src/lib/hooks/useBlockNumber.tsx index 10d38e555a8..1fc46e36d18 100644 --- a/apps/web/src/lib/hooks/useBlockNumber.tsx +++ b/apps/web/src/lib/hooks/useBlockNumber.tsx @@ -82,7 +82,7 @@ export function BlockNumberProvider({ children }: PropsWithChildren) { provider.removeListener('block', onBlock) } } - return + return undefined }, [provider, windowVisible, onChainBlock, multicallChainId]) // Poll once for the mainnet block number using the network provider. useEffect(() => { diff --git a/apps/web/src/lib/hooks/useInterval.ts b/apps/web/src/lib/hooks/useInterval.ts index 11be822f5d6..01b51237aac 100644 --- a/apps/web/src/lib/hooks/useInterval.ts +++ b/apps/web/src/lib/hooks/useInterval.ts @@ -11,7 +11,7 @@ import { useEffect } from 'react' export default function useInterval(callback: () => void | Promise, delay: null | number, leading = true) { useEffect(() => { if (delay === null) { - return + return undefined } let timeout: ReturnType diff --git a/apps/web/src/lib/hooks/useTokenList/sorting.ts b/apps/web/src/lib/hooks/useTokenList/sorting.ts index bbaadb2c4e8..772eb8abd55 100644 --- a/apps/web/src/lib/hooks/useTokenList/sorting.ts +++ b/apps/web/src/lib/hooks/useTokenList/sorting.ts @@ -66,7 +66,7 @@ export function getSortedPortfolioTokens( address, tokenBalance.token?.decimals, tokenBalance.token?.symbol, - tokenBalance.token?.project?.name ?? tokenBalance.token?.name, + tokenBalance.token?.name ?? tokenBalance.token?.project?.name, ) return portfolioToken diff --git a/apps/web/src/lib/utils/analytics.ts b/apps/web/src/lib/utils/analytics.ts index b718d21e2b0..17f0f36e100 100644 --- a/apps/web/src/lib/utils/analytics.ts +++ b/apps/web/src/lib/utils/analytics.ts @@ -1,7 +1,10 @@ import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' import { NATIVE_CHAIN_ID } from 'constants/tokens' -import { InterfaceTrade, QuoteMethod, SubmittableTrade } from 'state/routing/types' +import { InterfaceTrade, OffchainOrderType, QuoteMethod, SubmittableTrade, TradeFillType } from 'state/routing/types' import { isClassicTrade, isSubmittableTrade, isUniswapXTrade } from 'state/routing/utils' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__' +import { ClassicTrade, UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade' +import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing' import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails' import { computeRealizedPriceImpact } from 'utils/prices' @@ -42,38 +45,95 @@ function getEstimatedNetworkFee(trade: InterfaceTrade) { return undefined } +// eslint-disable-next-line consistent-return +function tradeRoutingToFillType({ + routing, + indicative, +}: { + routing: Routing + indicative: boolean +}): TradeFillType | 'bridge' { + if (indicative) { + return TradeFillType.None + } + + switch (routing) { + case Routing.DUTCH_V2: + case Routing.DUTCH_LIMIT: + case Routing.LIMIT_ORDER: + return TradeFillType.UniswapXv2 + case Routing.CLASSIC: + return TradeFillType.Classic + case Routing.BRIDGE: + return 'bridge' + } +} + +function tradeRoutingToOffchainOrderType(routing: Routing): OffchainOrderType | undefined { + switch (routing) { + case Routing.DUTCH_V2: + return OffchainOrderType.DUTCH_V2_AUCTION + case Routing.DUTCH_LIMIT: + case Routing.LIMIT_ORDER: + return OffchainOrderType.LIMIT_ORDER + default: + return undefined + } +} + export function formatCommonPropertiesForTrade( - trade: InterfaceTrade, + trade: InterfaceTrade | ClassicTrade | UniswapXTrade, allowedSlippage: Percent, outputFeeFiatValue?: number, ) { + const isUniversalSwapFlow = trade instanceof ClassicTrade || trade instanceof UniswapXTrade + return { - routing: trade.fillType, + routing: isUniversalSwapFlow ? tradeRoutingToFillType(trade) : trade.fillType, type: trade.tradeType, - ura_quote_id: isUniswapXTrade(trade) ? trade.quoteId : undefined, - ura_request_id: isSubmittableTrade(trade) ? trade.requestId : undefined, - ura_quote_block_number: isClassicTrade(trade) ? trade.blockNumber : undefined, + ura_quote_id: isUniversalSwapFlow ? trade.quote?.quote.quoteId : isUniswapXTrade(trade) ? trade.quoteId : undefined, + ura_request_id: isUniversalSwapFlow + ? trade.quote?.requestId + : isSubmittableTrade(trade) + ? trade.requestId + : undefined, + ura_quote_block_number: isUniversalSwapFlow + ? isClassic(trade) + ? trade.quote?.quote.blockNumber + : undefined + : isClassicTrade(trade) + ? trade.blockNumber + : undefined, token_in_address: getTokenAddress(trade.inputAmount.currency), token_out_address: getTokenAddress(trade.outputAmount.currency), 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), - price_impact_basis_points: isClassicTrade(trade) - ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) - : undefined, + price_impact_basis_points: + trade instanceof ClassicTrade || (!isUniversalSwapFlow && isClassicTrade(trade)) + ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) + : undefined, chain_id: trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId ? trade.inputAmount.currency.chainId : undefined, - estimated_network_fee_usd: getEstimatedNetworkFee(trade)?.toString(), + estimated_network_fee_usd: isUniversalSwapFlow + ? trade instanceof ClassicTrade + ? trade.quote?.quote.gasFeeUSD + : undefined + : getEstimatedNetworkFee(trade)?.toString(), minimum_output_after_slippage: trade.minimumAmountOut(allowedSlippage).toSignificant(6), allowed_slippage: formatPercentNumber(allowedSlippage), - method: getQuoteMethod(trade), + method: isUniversalSwapFlow ? undefined : getQuoteMethod(trade), fee_usd: outputFeeFiatValue, token_out_detected_tax: formatPercentNumber(trade.outputTax), token_in_detected_tax: formatPercentNumber(trade.inputTax), - offchain_order_type: isUniswapXTrade(trade) ? trade.offchainOrderType : undefined, + offchain_order_type: isUniversalSwapFlow + ? tradeRoutingToOffchainOrderType(trade.routing) + : isUniswapXTrade(trade) + ? trade.offchainOrderType + : undefined, transactionOriginType: TransactionOriginType.Internal, } } @@ -86,7 +146,7 @@ export const formatSwapSignedAnalyticsEventProperties = ({ timeToSignSinceRequestMs, portfolioBalanceUsd, }: { - trade: SubmittableTrade + trade: SubmittableTrade | ClassicTrade | UniswapXTrade allowedSlippage: Percent fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number } txHash?: string diff --git a/apps/web/src/nft/components/bag/BagRow.tsx b/apps/web/src/nft/components/bag/BagRow.tsx index 56243fb4f53..acf9d2bf7fa 100644 --- a/apps/web/src/nft/components/bag/BagRow.tsx +++ b/apps/web/src/nft/components/bag/BagRow.tsx @@ -280,7 +280,7 @@ export const UnavailableAssetsHeaderRow = ({ clearUnavailableAssets() setDidOpenUnavailableAssets(false) } - return + return undefined } const intervalId = setInterval(() => { diff --git a/apps/web/src/nft/components/bag/ButtonStates.tsx b/apps/web/src/nft/components/bag/ButtonStates.tsx index 447dc79d166..5aab1a9cb66 100644 --- a/apps/web/src/nft/components/bag/ButtonStates.tsx +++ b/apps/web/src/nft/components/bag/ButtonStates.tsx @@ -1,3 +1,4 @@ +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { DefaultTheme } from 'lib/styled-components' import { PriceImpact } from 'nft/hooks/usePriceImpact' import { ReactNode } from 'react' @@ -58,7 +59,7 @@ export function getBuyButtonStateData( ...defaultBuyButtonState, handleClick: handleClickOverride ?? (() => undefined), disabled: false, - buttonText: , + buttonText: , }, [BuyButtonStates.NOT_SUPPORTED_CHAIN]: { ...defaultBuyButtonState, diff --git a/apps/web/src/nft/components/card/utils.tsx b/apps/web/src/nft/components/card/utils.tsx index 6b39286f3e5..2abf8fb7d04 100644 --- a/apps/web/src/nft/components/card/utils.tsx +++ b/apps/web/src/nft/components/card/utils.tsx @@ -48,6 +48,7 @@ function getAssetMediaType(asset: GenieAsset | WalletAsset) { return assetMediaType } +// eslint-disable-next-line consistent-return export function getNftDisplayComponent( asset: GenieAsset | WalletAsset, mediaShouldBePlaying: boolean, @@ -127,7 +128,11 @@ export function useSelectAsset({ return } - return isSelected ? unselectAsset?.() : selectAsset?.() + if (isSelected) { + unselectAsset?.() + } else { + selectAsset?.() + } }, [selectAsset, isDisabled, onClick, unselectAsset, isSelected], ) diff --git a/apps/web/src/nft/components/collection/Sweep.tsx b/apps/web/src/nft/components/collection/Sweep.tsx index 69871c08c5c..6ef47f36f81 100644 --- a/apps/web/src/nft/components/collection/Sweep.tsx +++ b/apps/web/src/nft/components/collection/Sweep.tsx @@ -467,6 +467,7 @@ function useSweepFetcherParams( const isMarketFiltered = !!markets.length + // eslint-disable-next-line consistent-return return useMemo(() => { if (isMarketFiltered) { if (market === 'others') { diff --git a/apps/web/src/nft/components/details/AssetDetails.tsx b/apps/web/src/nft/components/details/AssetDetails.tsx index b637a4c1948..bce53ef0824 100644 --- a/apps/web/src/nft/components/details/AssetDetails.tsx +++ b/apps/web/src/nft/components/details/AssetDetails.tsx @@ -203,6 +203,7 @@ const AssetView = ({ mediaType: MediaType asset: GenieAsset dominantColor: [number, number, number] + // eslint-disable-next-line consistent-return }) => { const style = { ['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)` } diff --git a/apps/web/src/nft/hooks/useSendTransaction.ts b/apps/web/src/nft/hooks/useSendTransaction.ts index 558a025de70..c1ac8e3f883 100644 --- a/apps/web/src/nft/hooks/useSendTransaction.ts +++ b/apps/web/src/nft/hooks/useSendTransaction.ts @@ -88,7 +88,7 @@ export const useSendTransaction = create()( }) set({ state: TxStateType.Invalid }) } - return + return undefined } }, }), diff --git a/apps/web/src/nft/pages/profile/index.tsx b/apps/web/src/nft/pages/profile/index.tsx index 0a486f5600c..b7a0a9c6edc 100644 --- a/apps/web/src/nft/pages/profile/index.tsx +++ b/apps/web/src/nft/pages/profile/index.tsx @@ -1,6 +1,7 @@ import { InterfacePageName } from '@uniswap/analytics-events' import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' import { ButtonPrimary } from 'components/Button/buttons' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { useAccount } from 'hooks/useAccount' import useENSName from 'hooks/useENSName' import styled from 'lib/styled-components' @@ -117,7 +118,7 @@ export default function Profile() { - + diff --git a/apps/web/src/nft/utils/urlParams.ts b/apps/web/src/nft/utils/urlParams.ts index a37ee3ad292..e6d6fa8baf5 100644 --- a/apps/web/src/nft/utils/urlParams.ts +++ b/apps/web/src/nft/utils/urlParams.ts @@ -162,7 +162,7 @@ export const syncLocalFiltersWithURL = (state: CollectionFilters) => { export const applyFiltersFromURL = (location: Location, collectionStats: GenieCollection) => { if (!location.search) { - return + return undefined } const query = qs.parse(location.search, { diff --git a/apps/web/src/pages/AddLiquidity/AddLiquidityModal.tsx b/apps/web/src/pages/AddLiquidity/AddLiquidityModal.tsx index 277b51862e2..edfe0149a25 100644 --- a/apps/web/src/pages/AddLiquidity/AddLiquidityModal.tsx +++ b/apps/web/src/pages/AddLiquidity/AddLiquidityModal.tsx @@ -2,8 +2,9 @@ import { LiquidityModalDetailRows } from 'components/Liquidity/LiquidityModalDet import { LiquidityModalHeader } from 'components/Liquidity/LiquidityModalHeader' import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' import { AddLiquidityContextProvider, useAddLiquidityContext } from 'components/addLiquidity/AddLiquidityContext' -import { Field, InputForm } from 'components/addLiquidity/InputForm' +import { InputForm } from 'components/addLiquidity/InputForm' import { useCloseModal } from 'state/application/hooks' +import { PositionField } from 'types/position' import { Button, Flex } from 'ui/src' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalName } from 'uniswap/src/features/telemetry/constants' @@ -25,7 +26,7 @@ function AddLiquidityModalInner() { const token0 = currency0Amount.currency const token1 = currency1Amount.currency - const handleUserInput = (field: Field, newValue: string) => { + const handleUserInput = (field: PositionField, newValue: string) => { setAddLiquidityState((prev) => ({ ...prev, exactField: field, @@ -33,7 +34,7 @@ function AddLiquidityModalInner() { })) } - const handleOnSetMax = (field: Field, amount: string) => { + const handleOnSetMax = (field: PositionField, amount: string) => { setAddLiquidityState((prev) => ({ ...prev, exactField: field, diff --git a/apps/web/src/pages/AddLiquidity/index.tsx b/apps/web/src/pages/AddLiquidity/index.tsx index 20373915ec3..4d2e00c077c 100644 --- a/apps/web/src/pages/AddLiquidity/index.tsx +++ b/apps/web/src/pages/AddLiquidity/index.tsx @@ -21,6 +21,7 @@ import CurrencyInputPanel from 'components/CurrencyInputPanel' import FeeSelector from 'components/FeeSelector' import HoverInlineText from 'components/HoverInlineText' import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { AddRemoveTabs } from 'components/NavigationTabs' import { PositionPreview } from 'components/PositionPreview' import RangeSelector from 'components/RangeSelector' @@ -84,8 +85,6 @@ import { ThemedText } from 'theme/components' import { Text } from 'ui/src' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { Trans, t } from 'uniswap/src/i18n' @@ -543,10 +542,6 @@ function AddLiquidity() { }, [searchParams]) // END: sync values with query string - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - const Buttons = () => addIsUnsupported ? ( @@ -562,13 +557,7 @@ function AddLiquidity() { element={InterfaceElementName.CONNECT_WALLET_BUTTON} > - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) : ( diff --git a/apps/web/src/pages/AddLiquidityV2/index.tsx b/apps/web/src/pages/AddLiquidityV2/index.tsx index 234eb7dcc1a..dfcb2d1c01e 100644 --- a/apps/web/src/pages/AddLiquidityV2/index.tsx +++ b/apps/web/src/pages/AddLiquidityV2/index.tsx @@ -13,6 +13,7 @@ import { ButtonError, ButtonLight, ButtonPrimary } from 'components/Button/butto import { BlueCard, LightCard } from 'components/Card/cards' import CurrencyInputPanel from 'components/CurrencyInputPanel' import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { AddRemoveTabs } from 'components/NavigationTabs' import { MinimalPositionCard } from 'components/PositionCard' import { SwitchLocaleLink } from 'components/SwitchLocaleLink' @@ -47,8 +48,6 @@ import { useUserSlippageToleranceWithDefault } from 'state/user/hooks' import { ThemedText } from 'theme/components' import { Text } from 'ui/src' import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { Trans } from 'uniswap/src/i18n' @@ -354,10 +353,6 @@ export default function AddLiquidity() { const addIsUnsupported = useIsSwapUnsupported(currencies?.CURRENCY_A, currencies?.CURRENCY_B) - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - if (!networkSupportsV2) { return } @@ -474,13 +469,7 @@ export default function AddLiquidity() { element={InterfaceElementName.CONNECT_WALLET_BUTTON} > - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) : ( diff --git a/apps/web/src/pages/App/Header.tsx b/apps/web/src/pages/App/Header.tsx index ad58e6e7f8a..289d5c1a56f 100644 --- a/apps/web/src/pages/App/Header.tsx +++ b/apps/web/src/pages/App/Header.tsx @@ -7,9 +7,9 @@ import { useBag } from 'nft/hooks' import { GRID_AREAS } from 'pages/App/utils/shared' import { memo } from 'react' import { useLocation } from 'react-router-dom' -import { NAV_HEIGHT } from 'theme' import { Z_INDEX } from 'theme/zIndex' import { useIsTouchDevice } from 'ui/src' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' const AppHeader = styled.div` grid-area: ${GRID_AREAS.HEADER}; @@ -18,6 +18,11 @@ const AppHeader = styled.div` position: sticky; top: 0px; z-index: ${Z_INDEX.sticky}; + pointer-events: none; + + & > * { + pointer-events: auto; + } ` const Banners = styled.div` position: relative; @@ -28,7 +33,7 @@ const NavOnScroll = styled.div<{ $hide: boolean; $transparent?: boolean }>` transition: transform ${({ theme }) => theme.transition.duration.slow}; background-color: ${({ theme, $transparent }) => !$transparent && theme.surface1}; border-bottom: ${({ theme, $transparent }) => !$transparent && `1px solid ${theme.surface3}`}; - ${({ $hide }) => $hide && `transform: translateY(-${NAV_HEIGHT}px);`} + ${({ $hide }) => $hide && `transform: translateY(-${INTERFACE_NAV_HEIGHT}px);`} ` export const Header = memo(function Header() { diff --git a/apps/web/src/pages/Landing/LandingV2.tsx b/apps/web/src/pages/Landing/LandingV2.tsx index f3b0a7d7ddd..69289f291e4 100644 --- a/apps/web/src/pages/Landing/LandingV2.tsx +++ b/apps/web/src/pages/Landing/LandingV2.tsx @@ -1,7 +1,7 @@ import { Hero } from 'pages/Landing/sections/Hero' import { Suspense, lazy, memo, useRef } from 'react' -import { NAV_HEIGHT } from 'theme' import { Flex, styled } from 'ui/src' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' // The Fold is always loaded, but is lazy-loaded because it is not seen without user interaction. // Annotating it with webpackPreload allows it to be ready when requested. @@ -29,7 +29,13 @@ function LandingV2({ transition }: { transition?: boolean }) { } return ( - + diff --git a/apps/web/src/pages/Landing/sections/Hero.tsx b/apps/web/src/pages/Landing/sections/Hero.tsx index 5715750cef0..de2a698233d 100644 --- a/apps/web/src/pages/Landing/sections/Hero.tsx +++ b/apps/web/src/pages/Landing/sections/Hero.tsx @@ -4,11 +4,14 @@ import { useScroll } from 'hooks/useScroll' import { TokenCloud } from 'pages/Landing/components/TokenCloud' import { Hover, RiseIn, RiseInText } from 'pages/Landing/components/animations' import { Swap } from 'pages/Swap' -import { Fragment } from 'react' +import { Fragment, useCallback } from 'react' import { ChevronDown } from 'react-feather' -import { NAV_HEIGHT } from 'theme' +import { useNavigate } from 'react-router-dom' +import { serializeSwapStateToURLParameters } from 'state/swap/hooks' import { Flex, Text } from 'ui/src' +import { SwapRedirectFn } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' import { Trans, useTranslation } from 'uniswap/src/i18n' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' import { UniverseChainId } from 'uniswap/src/types/chains' interface HeroProps { @@ -19,11 +22,27 @@ interface HeroProps { export function Hero({ scrollToRef, transition }: HeroProps) { const { height: scrollPosition } = useScroll() const initialInputCurrency = useCurrency('ETH') + const navigate = useNavigate() const { t } = useTranslation() const translateY = -scrollPosition / 7 const opacityY = 1 - scrollPosition / 1000 + const swapRedirectCallback = useCallback( + ({ inputCurrency, outputCurrency, typedValue, independentField, chainId }: Parameters[0]) => { + navigate( + `/swap${serializeSwapStateToURLParameters({ + inputCurrency, + outputCurrency, + typedValue, + independentField, + chainId, + })}`, + ) + }, + [navigate], + ) + return ( @@ -89,11 +108,13 @@ export function Hero({ scrollToRef, transition }: HeroProps) { maxWidth="100%" > diff --git a/apps/web/src/pages/LegacyPool/NewPosition.tsx b/apps/web/src/pages/LegacyPool/NewPosition.tsx deleted file mode 100644 index d9cd754809c..00000000000 --- a/apps/web/src/pages/LegacyPool/NewPosition.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Navigate } from 'react-router-dom' -import { FeatureFlags } from 'uniswap/src/features/gating/flags' -import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' - -export const NewPosition = () => { - const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) - - if (!isLoading && !v4Enabled) { - return - } - - if (isLoading) { - return null - } - - return ( -
-

New Position

-
- ) -} diff --git a/apps/web/src/pages/LegacyPool/PositionPage.tsx b/apps/web/src/pages/LegacyPool/PositionPage.tsx index c4437e7a0b2..a903037588c 100644 --- a/apps/web/src/pages/LegacyPool/PositionPage.tsx +++ b/apps/web/src/pages/LegacyPool/PositionPage.tsx @@ -7,6 +7,7 @@ import Badge from 'components/Badge/Badge' import RangeBadge from 'components/Badge/RangeBadge' import { ButtonConfirmed, ButtonGray, ButtonPrimary, SmallButtonPrimary } from 'components/Button/buttons' import { DarkCard, LightCard } from 'components/Card/cards' +import { PositionNFT } from 'components/Liquidity/PositionNFT' import { LoadingFullscreen } from 'components/Loader/styled' import CurrencyLogo from 'components/Logo/CurrencyLogo' import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' @@ -38,7 +39,7 @@ import { useSingleCallResult } from 'lib/hooks/multicall' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import styled, { useTheme } from 'lib/styled-components' import { LoadingRows } from 'pages/LegacyPool/styled' -import { PropsWithChildren, useCallback, useMemo, useRef, useState } from 'react' +import { PropsWithChildren, useCallback, useMemo, useState } from 'react' import { Helmet } from 'react-helmet-async/lib/index' import { Link, useParams } from 'react-router-dom' import { Bound } from 'state/mint/v3/actions' @@ -157,23 +158,6 @@ const ResponsiveButtonConfirmed = styled(ButtonConfirmed)` } ` -const NFTGrid = styled.div` - display: grid; - grid-template: 'overlap'; - min-height: 400px; -` - -const NFTCanvas = styled.canvas` - grid-area: overlap; -` - -const NFTImage = styled.img` - grid-area: overlap; - height: 400px; - /* Ensures SVG appears on top of canvas. */ - z-index: 1; -` - const StyledPoolLink = styled(Link)` text-decoration: none; ${ClickableStyle} @@ -276,65 +260,6 @@ function getRatio( } } -// snapshots a src img into a canvas -function getSnapshot(src: HTMLImageElement, canvas: HTMLCanvasElement, targetHeight: number) { - const context = canvas.getContext('2d') - - if (context) { - let { width, height } = src - - // src may be hidden and not have the target dimensions - const ratio = width / height - height = targetHeight - width = Math.round(ratio * targetHeight) - - // Ensure crispness at high DPIs - canvas.width = width * devicePixelRatio - canvas.height = height * devicePixelRatio - canvas.style.width = width + 'px' - canvas.style.height = height + 'px' - context.scale(devicePixelRatio, devicePixelRatio) - - context.clearRect(0, 0, width, height) - context.drawImage(src, 0, 0, width, height) - } -} - -function NFT({ image, height: targetHeight }: { image: string; height: number }) { - const [animate, setAnimate] = useState(false) - - const canvasRef = useRef(null) - const imageRef = useRef(null) - - return ( - { - setAnimate(true) - }} - onMouseLeave={() => { - // snapshot the current frame so the transition to the canvas is smooth - if (imageRef.current && canvasRef.current) { - getSnapshot(imageRef.current, canvasRef.current, targetHeight) - } - setAnimate(false) - }} - > - - - ) -} - const useInverter = ({ priceLower, priceUpper, @@ -396,12 +321,12 @@ const PositionLabelRow = styled(RowFixed)({ function parseTokenId(tokenId: string | undefined): BigNumber | undefined { if (!tokenId) { - return + return undefined } try { return BigNumber.from(tokenId) } catch (error) { - return + return undefined } } @@ -797,7 +722,7 @@ function PositionPageContent() { minWidth: '340px', }} > - + {typeof account.chainId === 'number' && owner && !ownsNFT ? ( diff --git a/apps/web/src/pages/MigrateV3/index.tsx b/apps/web/src/pages/MigrateV3/index.tsx index 87e7718a3e2..e5f2a5a10e5 100644 --- a/apps/web/src/pages/MigrateV3/index.tsx +++ b/apps/web/src/pages/MigrateV3/index.tsx @@ -1,11 +1,17 @@ import { BreadcrumbNavContainer, BreadcrumbNavLink } from 'components/BreadcrumbNav' import { LiquidityPositionCard } from 'components/Liquidity/LiquidityPositionCard' -import { usePositionInfo } from 'components/Liquidity/utils' +import { PositionInfo, usePositionInfo } from 'components/Liquidity/utils' import { PoolProgressIndicator } from 'components/PoolProgressIndicator/PoolProgressIndicator' -import { useState } from 'react' +import { PriceRangeContextProvider, useCreatePositionContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { CreatePositionContextProvider } from 'pages/Pool/Positions/create/CreatePositionContextProvider' +import { EditSelectTokensStep } from 'pages/Pool/Positions/create/EditStep' +import { SelectPriceRangeStep } from 'pages/Pool/Positions/create/RangeSelectionStep' +import { SelectTokensStep } from 'pages/Pool/Positions/create/SelectTokenStep' +import { PositionFlowStep } from 'pages/Pool/Positions/create/types' import { ChevronRight } from 'react-feather' import { Navigate, useParams } from 'react-router-dom' import { ClickableTamaguiStyle } from 'theme/components' +import { PositionField } from 'types/position' import { Flex, Main, Text, styled } from 'ui/src' import { ArrowDown } from 'ui/src/components/icons/ArrowDown' import { RotateLeft } from 'ui/src/components/icons/RotateLeft' @@ -26,29 +32,12 @@ const BodyWrapper = styled(Main, { p: 24, }) -enum MigrateStep { - SELECT_FEE_TIER, - SET_PRICE_RANGE, -} - -/** - * The page for migrating any v3 LP position to v4. - */ -export default function MigrateV3() { +function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) { const { positionId } = useParams<{ positionId: string }>() const { t } = useTranslation() - const [step, setStep] = useState(MigrateStep.SELECT_FEE_TIER) - const { value: v4Enabled, isLoading: isV4GateLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) - - // TODO(WEB-4920): replace this with real data fetching - const { data } = useGetPositionsQuery() - const position = data?.positions[2] - const positionInfo = usePositionInfo(position) - if (!position || !positionInfo) { - // TODO(WEB-4920): handle loading/error states (including if the position is for v2) - return null - } + const { step, setStep } = useCreatePositionContext() + const { value: v4Enabled, isLoading: isV4GateLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) const { currency0Amount, currency1Amount } = positionInfo @@ -80,8 +69,8 @@ export default function MigrateV3() {
@@ -99,7 +88,7 @@ export default function MigrateV3() { px="$padding12" {...ClickableTamaguiStyle} onPress={() => { - setStep(MigrateStep.SELECT_FEE_TIER) + setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) // reset any other state here. }} > @@ -109,15 +98,65 @@ export default function MigrateV3() {
- + + + {step === PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER ? ( + { + setStep(PositionFlowStep.PRICE_RANGE) + }} + /> + ) : ( + + )} + {step === PositionFlowStep.PRICE_RANGE && ( + { + // TODO (WEB-4920): submit the migration transaction. + }} + /> + )}
- {/* TODO: fee tier selection component. collapse if step === SET_PRICE_RANGE */} - {/* TODO: price range component. hide if step === SELECT_FEE_TIER */} ) } + +/** + * The page for migrating any v3 LP position to v4. + */ +export default function MigrateV3() { + // TODO(WEB-4920): replace this with real data fetching + const { data } = useGetPositionsQuery() + const position = data?.positions[1] + const positionInfo = usePositionInfo(position) + + if (!position || !positionInfo) { + // TODO(WEB-4920): handle loading/error states (including if the position is for v2) + return null + } + const { currency0Amount, currency1Amount } = positionInfo + return ( + + + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/PositionPage.tsx b/apps/web/src/pages/Pool/Positions/PositionPage.tsx new file mode 100644 index 00000000000..86e2e2fc34b --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/PositionPage.tsx @@ -0,0 +1,196 @@ +import { Price } from '@uniswap/sdk-core' +import { BreadcrumbNavContainer, BreadcrumbNavLink } from 'components/BreadcrumbNav' +import { LiquidityPositionAmountsTile } from 'components/Liquidity/LiquidityPositionAmountsTile' +import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' +import { LiquidityPositionPriceRangeTile } from 'components/Liquidity/LiquidityPositionPriceRangeTile' +import { PositionNFT } from 'components/Liquidity/PositionNFT' +import { usePositionInfo } from 'components/Liquidity/utils' +import { LoadingFullscreen } from 'components/Loader/styled' +import { usePositionTokenURI } from 'hooks/usePositionTokenURI' +import { useState } from 'react' +import { ChevronRight } from 'react-feather' +import { Navigate, useParams } from 'react-router-dom' +import { setOpenModal } from 'state/application/reducer' +import { useAppDispatch } from 'state/hooks' +import { ClickableTamaguiStyle } from 'theme/components' +import { Flex, Main, Switch, Text, styled } from 'ui/src' +import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' +import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { Trans } from 'uniswap/src/i18n' + +const BodyWrapper = styled(Main, { + backgroundColor: '$surface1', + display: 'flex', + flexDirection: 'column', + gap: '$spacing32', + mx: 'auto', + width: '100%', + zIndex: '$default', + p: '$spacing24', +}) + +// TODO: replace with Spore button once available +export const HeaderButton = styled(Flex, { + row: true, + backgroundColor: '$surface2', + borderRadius: '$rounded12', + alignItems: 'center', + justifyContent: 'center', + gap: '$gap4', + py: '$padding8', + px: '$padding12', + ...ClickableTamaguiStyle, + variants: { + emphasis: { + primary: { + backgroundColor: '$accent3', + }, + secondary: { + backgroundColor: '$surface2', + }, + }, + } as const, +}) + +export default function PositionPage() { + const { positionId } = useParams<{ positionId: string }>() + // const { t } = useTranslation() + // TODO(WEB-4920): replace this with real query fetching the position by ID + const { data } = useGetPositionsQuery() + const position = data?.positions[1] + const positionInfo = usePositionInfo(position) + const metadata = usePositionTokenURI(positionId ? parseInt(positionId) : undefined) + + const dispatch = useAppDispatch() + const [collectAsWeth, setCollectAsWeth] = useState(false) + + const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) + + if (!isLoading && !v4Enabled) { + return + } + + if (!position || !positionInfo) { + // TODO(WEB-4920): handle loading/error states + return null + } + + const { currency0Amount, currency1Amount, status } = positionInfo + + return ( + + + + + + + + + + + + + { + dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: position })) + }} + > + + + + + { + dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: position })) + }} + > + + + + + + + + + + {'result' in metadata ? ( + + ) : ( + + )} + + + + + + + + {/* TODO(WEB-4920): get est. USD value of position */} + $5678.90 + + + + + + + + + { + // TODO(WEB-4920): open claim fees modal + }} + > + + + + + + + $1,084.61 + + + + + + + { + setCollectAsWeth((prev) => !prev) + }} + /> + + + + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx b/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx new file mode 100644 index 00000000000..1b77e08cea7 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx @@ -0,0 +1,175 @@ +import { Percent } from '@uniswap/sdk-core' +import { BreadcrumbNavContainer, BreadcrumbNavLink } from 'components/BreadcrumbNav' +import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' +import { usePositionInfo } from 'components/Liquidity/utils' +import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo' +import { useTotalSupply } from 'hooks/useTotalSupply' +import JSBI from 'jsbi' +import { useTokenBalance } from 'lib/hooks/useCurrencyBalance' +import { HeaderButton } from 'pages/Pool/Positions/PositionPage' +import { ChevronRight } from 'react-feather' +import { Navigate, useNavigate } from 'react-router-dom' +import { setOpenModal } from 'state/application/reducer' +import { useAppDispatch } from 'state/hooks' +import { Flex, Main, Text, styled } from 'ui/src' +import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' +import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { Trans } from 'uniswap/src/i18n' +import { useFormatter } from 'utils/formatNumbers' +import { useAccount } from 'wagmi' + +const BodyWrapper = styled(Main, { + backgroundColor: '$surface1', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + mx: 'auto', + width: '100%', + zIndex: '$default', + p: '$spacing24', +}) + +export default function V2PositionPage() { + // const { currencyIdA, currencyIdB } = useParams<{ positionId: string }>() + // TODO(WEB-4920): replace this with real query fetching the position by the params above + const { data } = useGetPositionsQuery() + const position = data?.positions[0] + const positionInfo = usePositionInfo(position) + const dispatch = useAppDispatch() + const navigate = useNavigate() + const account = useAccount() + const { formatPercent } = useFormatter() + + const userDefaultPoolBalance = useTokenBalance(account.address, positionInfo?.liquidityToken) + const totalPoolTokens = useTotalSupply(positionInfo?.liquidityToken) + + const poolTokenPercentage = + !!userDefaultPoolBalance && + !!totalPoolTokens && + JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userDefaultPoolBalance.quotient) + ? new Percent(userDefaultPoolBalance.quotient, totalPoolTokens.quotient) + : undefined + + const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) + + if (!isLoading && !v4Enabled) { + return + } + + if (!position || !positionInfo) { + // TODO(WEB-4920): handle loading/error states + return null + } + + const { currency0Amount, currency1Amount } = positionInfo + + return ( + + + + + + + + + + + + + { + navigate('/migrate/v2') + }} + > + + + + + { + dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: position })) + }} + > + + + + + { + dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: position })) + }} + > + + + + + + + + + + + {/* TODO(WEB-4920): get real USD position value */} + $1482.21 + + + + + + + {userDefaultPoolBalance ? userDefaultPoolBalance.toSignificant(4) : '-'} + + + + + + + + + {currency0Amount.toSignificant(4)} + + + + + + + + + {currency1Amount.toSignificant(4)} + + + + + + + + {formatPercent(poolTokenPercentage)} + + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/AddHook.tsx b/apps/web/src/pages/Pool/Positions/create/AddHook.tsx new file mode 100644 index 00000000000..426aff323d8 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/AddHook.tsx @@ -0,0 +1,60 @@ +import { AdvancedButton } from 'pages/Pool/Positions/create/shared' +import { useState } from 'react' +import { Button } from 'ui/src' +import { DocumentList } from 'ui/src/components/icons/DocumentList' +import { X } from 'ui/src/components/icons/X' +import { Flex } from 'ui/src/components/layout/Flex' +import { fonts } from 'ui/src/theme' +import { TextInput } from 'uniswap/src/components/input/TextInput' +import { useTranslation } from 'uniswap/src/i18n' + +export function AddHook() { + const { t } = useTranslation() + + const [hookInputEnabled, setHookInputEnabled] = useState(false) + const [hookAddress, setHookAddress] = useState('') + + const handleToggleHookInput = () => { + setHookInputEnabled((prev) => !prev) + setHookAddress('') + } + + if (hookInputEnabled) { + return ( + + setHookAddress(text)} + /> + + + ) + } + + return +} diff --git a/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx b/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx new file mode 100644 index 00000000000..5445f2a6c18 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx @@ -0,0 +1,242 @@ +/* eslint-disable-next-line no-restricted-imports */ +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { getProtocolVersionFromString, getProtocolVersionLabel } from 'components/Liquidity/utils' +import { PoolProgressIndicator } from 'components/PoolProgressIndicator/PoolProgressIndicator' +import { + DEFAULT_PRICE_RANGE_STATE, + PriceRangeContextProvider, + useCreatePositionContext, + usePriceRangeContext, +} from 'pages/Pool/Positions/create/CreatePositionContext' +import { CreatePositionContextProvider } from 'pages/Pool/Positions/create/CreatePositionContextProvider' +import { EditRangeSelectionStep, EditSelectTokensStep } from 'pages/Pool/Positions/create/EditStep' +import { SelectPriceRangeStep } from 'pages/Pool/Positions/create/RangeSelectionStep' +import { SelectTokensStep } from 'pages/Pool/Positions/create/SelectTokenStep' +import { DEFAULT_POSITION_STATE, PositionFlowStep } from 'pages/Pool/Positions/create/types' +import { useCallback, useMemo } from 'react' +import { Navigate, useParams } from 'react-router-dom' +import { Button, Flex, Text } from 'ui/src' +import { AngleRightSmall } from 'ui/src/components/icons/AngleRightSmall' +import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' +import { RotateLeft } from 'ui/src/components/icons/RotateLeft' +import { Settings } from 'ui/src/components/icons/Settings' +import { iconSizes } from 'ui/src/theme/iconSizes' +import { ActionSheetDropdown } from 'uniswap/src/components/dropdowns/ActionSheetDropdown' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' +import { Trans, useTranslation } from 'uniswap/src/i18n' + +function CreatePositionInner() { + const { + positionState: { protocolVersion }, + step, + setStep, + } = useCreatePositionContext() + const v2Selected = protocolVersion === ProtocolVersion.V2 + + const handleContinue = useCallback(() => { + if (v2Selected) { + setStep(PositionFlowStep.DEPOSIT) + } else { + setStep((prevStep) => prevStep + 1) + } + }, [setStep, v2Selected]) + + return ( + + {step === PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER ? ( + + ) : step === PositionFlowStep.PRICE_RANGE ? ( + <> + + + + ) : ( + <> + + {!v2Selected && } + + )} + + ) +} + +const Sidebar = () => { + const { t } = useTranslation() + const { + positionState: { protocolVersion }, + step, + } = useCreatePositionContext() + + const PoolProgressSteps = useMemo(() => { + if (protocolVersion === ProtocolVersion.V2) { + return [ + { label: t(`position.step.select`), active: step === PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER }, + { label: t(`position.step.deposit`), active: step == PositionFlowStep.DEPOSIT }, + ] + } + + return [ + { label: t(`position.step.select`), active: step === PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER }, + { label: t(`position.step.range`), active: step === PositionFlowStep.PRICE_RANGE }, + { label: t(`position.step.deposit`), active: step == PositionFlowStep.DEPOSIT }, + ] + }, [protocolVersion, step, t]) + + return ( + + + + + + + + + + + + + + + ) +} + +const Toolbar = () => { + const { + positionState: { protocolVersion }, + setPositionState, + setStep, + } = useCreatePositionContext() + const { setPriceRangeState } = usePriceRangeContext() + + const handleReset = useCallback(() => { + setPositionState(DEFAULT_POSITION_STATE) + setPriceRangeState(DEFAULT_PRICE_RANGE_STATE) + setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) + }, [setPositionState, setPriceRangeState, setStep]) + + const handleVersionChange = useCallback( + (version: ProtocolVersion) => { + setPositionState(() => ({ + ...DEFAULT_POSITION_STATE, + protocolVersion: version, + })) + setPriceRangeState(DEFAULT_PRICE_RANGE_STATE) + setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) + }, + [setPositionState, setPriceRangeState, setStep], + ) + + const versionOptions = useMemo( + () => + [ProtocolVersion.V2, ProtocolVersion.V3, ProtocolVersion.V4] + .filter((version) => version != protocolVersion) + .map((version) => ({ + key: `version-${version}`, + onPress: () => handleVersionChange(version), + render: () => ( + + + + + + ), + })), + [handleVersionChange, protocolVersion], + ) + + return ( + + + + + + + + ) +} + +export function CreatePosition() { + const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) + const { protocolVersion } = useParams<{ protocolVersion: string }>() + + if (!isLoading && !v4Enabled) { + return + } + + if (isLoading) { + return null + } + return ( + + + + + + + + + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx b/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx new file mode 100644 index 00000000000..45a3d9a681f --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx @@ -0,0 +1,49 @@ +/* eslint-disable-next-line no-restricted-imports */ +import { + CreatePositionContextType, + DEFAULT_POSITION_STATE, + PositionFlowStep, + PriceRangeContextType, + PriceRangeState, +} from 'pages/Pool/Positions/create/types' +import React, { useContext, useState } from 'react' + +export const CreatePositionContext = React.createContext({ + step: PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER, + setStep: () => undefined, + positionState: DEFAULT_POSITION_STATE, + setPositionState: () => undefined, + feeTierSearchModalOpen: false, + setFeeTierSearchModalOpen: () => undefined, + derivedPositionInfo: { + pool: undefined, + }, +}) + +export const useCreatePositionContext = () => { + return useContext(CreatePositionContext) +} + +export const DEFAULT_PRICE_RANGE_STATE: PriceRangeState = { + priceInverted: false, + fullRange: true, + minPrice: '0', + maxPrice: 'INF', +} + +const PriceRangeContext = React.createContext({ + priceRangeState: DEFAULT_PRICE_RANGE_STATE, + setPriceRangeState: () => undefined, +}) + +export const usePriceRangeContext = () => { + return useContext(PriceRangeContext) +} + +export function PriceRangeContextProvider({ children }: { children: React.ReactNode }) { + const [priceRangeState, setPriceRangeState] = useState(DEFAULT_PRICE_RANGE_STATE) + + return ( + {children} + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/CreatePositionContextProvider.tsx b/apps/web/src/pages/Pool/Positions/create/CreatePositionContextProvider.tsx new file mode 100644 index 00000000000..c0248087865 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/CreatePositionContextProvider.tsx @@ -0,0 +1,35 @@ +import { FeeTierSearchModal } from 'components/Liquidity/FeeTierSearchModal' +import { CreatePositionContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { useDerivedPositionInfo } from 'pages/Pool/Positions/create/hooks' +import { DEFAULT_POSITION_STATE, PositionFlowStep, PositionState } from 'pages/Pool/Positions/create/types' +import { useState } from 'react' + +export function CreatePositionContextProvider({ + children, + initialState = {}, +}: { + children: React.ReactNode + initialState?: Partial +}) { + const [positionState, setPositionState] = useState({ ...DEFAULT_POSITION_STATE, ...initialState }) + const [step, setStep] = useState(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) + const derivedPositionInfo = useDerivedPositionInfo(positionState) + const [feeTierSearchModalOpen, setFeeTierSearchModalOpen] = useState(false) + + return ( + + {children} + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/EditStep.tsx b/apps/web/src/pages/Pool/Positions/create/EditStep.tsx new file mode 100644 index 00000000000..604f1efab00 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/EditStep.tsx @@ -0,0 +1,93 @@ +import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' +import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { Container } from 'pages/Pool/Positions/create/shared' +import { PositionFlowStep } from 'pages/Pool/Positions/create/types' +import { useCallback } from 'react' +import { Button, Flex, FlexProps, Text } from 'ui/src' +import { Edit } from 'ui/src/components/icons/Edit' +import { iconSizes } from 'ui/src/theme' +import { Trans } from 'uniswap/src/i18n' + +const EditStep = ({ children, onClick, ...rest }: { children: JSX.Element; onClick: () => void } & FlexProps) => { + return ( + + {children} + + + ) +} + +export const EditSelectTokensStep = (props?: FlexProps) => { + const { + positionState: { + tokenInputs: { TOKEN0: token0, TOKEN1: token1 }, + }, + setStep, + } = useCreatePositionContext() + const currencies = [token0, token1] + + const handleEdit = useCallback(() => { + setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) + }, [setStep]) + + return ( + + + + + {token0?.symbol} + / + {token1?.symbol} + + + + ) +} + +export const EditRangeSelectionStep = (props?: FlexProps) => { + const { + positionState: { + tokenInputs: { TOKEN0: token0, TOKEN1: token1 }, + }, + setStep, + } = useCreatePositionContext() + const { + priceRangeState: { priceInverted }, + } = usePriceRangeContext() + + const baseCurrency = priceInverted ? token1 : token0 + const quoteCurrency = priceInverted ? token0 : token1 + + const handleEdit = useCallback(() => { + setStep(PositionFlowStep.PRICE_RANGE) + }, [setStep]) + + return ( + + + + + + + + + + + {`283,923,000 ${baseCurrency?.symbol + '/' + quoteCurrency?.symbol}`} + + + + + + {`481,848,481 ${baseCurrency?.symbol + '/' + quoteCurrency?.symbol}`} + + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx b/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx new file mode 100644 index 00000000000..eda93cab403 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx @@ -0,0 +1,234 @@ +import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { Container } from 'pages/Pool/Positions/create/shared' +import { useCallback, useMemo } from 'react' +import { Minus, Plus } from 'react-feather' +import { Button, Flex, FlexProps, SegmentedControl, Text, useSporeColors } from 'ui/src' +import { SwapActionButton } from 'ui/src/components/icons/SwapActionButton' +import { fonts } from 'ui/src/theme' +import { AmountInput } from 'uniswap/src/components/CurrencyInputPanel/AmountInput' +import { Trans, useTranslation } from 'uniswap/src/i18n' + +enum RangeSelectionInput { + MIN, + MAX, +} + +enum RangeSelection { + FULL = 'FULL', + CUSTOM = 'CUSTOM', +} + +function RangeControl({ value, active }: { value: string; active: boolean }) { + return ( + + {value} + + ) +} + +function RangeInput({ input }: { input: RangeSelectionInput }) { + const colors = useSporeColors() + const { t } = useTranslation() + + const { + positionState: { + tokenInputs: { TOKEN0: token0, TOKEN1: token1 }, + }, + } = useCreatePositionContext() + const { + priceRangeState: { minPrice, maxPrice, priceInverted }, + setPriceRangeState, + } = usePriceRangeContext() + + const baseCurrency = priceInverted ? token1 : token0 + const quoteCurrency = priceInverted ? token0 : token1 + + const handlePriceRangeInput = useCallback( + (input: RangeSelectionInput, value: string) => { + if (input === RangeSelectionInput.MIN) { + setPriceRangeState((prev) => ({ ...prev, minPrice: value })) + } else { + setPriceRangeState((prev) => ({ ...prev, maxPrice: value })) + } + }, + [setPriceRangeState], + ) + + return ( + + + + {input === RangeSelectionInput.MIN ? t(`pool.minPrice`) : t(`pool.maxPrice`)} + + handlePriceRangeInput(input, text)} + /> + + + + + + + + + + ) +} + +export const SelectPriceRangeStep = ({ onContinue, ...rest }: { onContinue: () => void } & FlexProps) => { + const { t } = useTranslation() + + const { + positionState: { + tokenInputs: { TOKEN0: token0, TOKEN1: token1 }, + }, + } = useCreatePositionContext() + const { + priceRangeState: { priceInverted, fullRange }, + setPriceRangeState, + } = usePriceRangeContext() + + const baseCurrency = priceInverted ? token1 : token0 + const quoteCurrency = priceInverted ? token0 : token1 + + const controlOptions = useMemo(() => { + return [{ value: token0?.symbol ?? '' }, { value: token1?.symbol ?? '' }] + }, [token0?.symbol, token1?.symbol]) + + const handleSelectToken = useCallback( + (option: string) => { + if (option === token0?.symbol) { + setPriceRangeState((prevState) => ({ ...prevState, priceInverted: false })) + } else { + setPriceRangeState((prevState) => ({ ...prevState, priceInverted: true })) + } + }, + [token0?.symbol, setPriceRangeState], + ) + + const handleSelectRange = useCallback( + (option: RangeSelection) => { + if (option === RangeSelection.FULL) { + setPriceRangeState((prevState) => ({ ...prevState, fullRange: true })) + } else { + setPriceRangeState((prevState) => ({ ...prevState, fullRange: false })) + } + }, + [setPriceRangeState], + ) + + const segmentedControlRangeOptions = [ + { display: , value: RangeSelection.FULL }, + { display: , value: RangeSelection.CUSTOM }, + ] + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/SelectTokenStep.tsx b/apps/web/src/pages/Pool/Positions/create/SelectTokenStep.tsx new file mode 100644 index 00000000000..fce536207e6 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/SelectTokenStep.tsx @@ -0,0 +1,301 @@ +// eslint-disable-next-line no-restricted-imports +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Currency, Percent } from '@uniswap/sdk-core' +import { FeeAmount } from '@uniswap/v3-sdk' +import { PositionInfoBadge } from 'components/Liquidity/LiquidityPositionInfoBadges' +import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo' +import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal' +import { useCurrencyInfo } from 'hooks/Tokens' +import { AddHook } from 'pages/Pool/Positions/create/AddHook' +import { useCreatePositionContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { AdvancedButton, Container } from 'pages/Pool/Positions/create/shared' +import { useCallback, useReducer, useState } from 'react' +import { TamaguiClickableStyle } from 'theme/components' +import { PositionField } from 'types/position' +import { Button, Flex, FlexProps, Text, styled } from 'ui/src' +import { CheckCircleFilled } from 'ui/src/components/icons/CheckCircleFilled' +import { Dollar } from 'ui/src/components/icons/Dollar' +import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' +import { iconSizes } from 'ui/src/theme' +import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' +import { Trans, useTranslation } from 'uniswap/src/i18n' +import { useFormatter } from 'utils/formatNumbers' + +const CurrencySelector = ({ currency, onPress }: { currency?: Currency; onPress: () => void }) => { + const { t } = useTranslation() + // TODO: remove when backend returns token logos in graphql response: WEB-4920 + const currencyInfo = useCurrencyInfo(currency) + + return ( + + ) +} + +interface FeeTier { + value: number + title: string + selectionPercent: number +} + +const FeeTierContainer = styled(Flex, { + flex: 1, + width: '100%', + p: '$spacing12', + gap: '$spacing8', + justifyContent: 'space-between', + borderRadius: '$rounded12', + borderWidth: 1, + borderColor: '$surface3', + ...TamaguiClickableStyle, +}) + +const FeeTier = ({ + feeTier, + selected, + onSelect, +}: { + feeTier: FeeTier + selected: boolean + onSelect: (value: number) => void +}) => { + return ( + onSelect(feeTier.value)} background={selected ? '$surface3' : '$surface1'}> + + {feeTier.value / 10000}% + {selected && } + + {feeTier.title} + + {feeTier.selectionPercent}% select + + + ) +} + +export function SelectTokensStep({ + onContinue, + tokensLocked, + ...rest +}: { tokensLocked?: boolean; onContinue: () => void } & FlexProps) { + const { formatPercent } = useFormatter() + const { t } = useTranslation() + + const { + positionState: { + tokenInputs: { TOKEN0: token0, TOKEN1: token1 }, + fee, + protocolVersion, + }, + setPositionState, + derivedPositionInfo, + setFeeTierSearchModalOpen, + } = useCreatePositionContext() + + const [currencySearchInputState, setCurrencySearchInputState] = useState(undefined) + const [isShowMoreFeeTiersEnabled, toggleShowMoreFeeTiersEnabled] = useReducer((state) => !state, false) + const continueButtonEnabled = !!derivedPositionInfo.pool + + const handleCurrencySelect = useCallback( + (currency: Currency) => { + switch (currencySearchInputState) { + case PositionField.TOKEN0: + case PositionField.TOKEN1: + setPositionState((prevState) => ({ + ...prevState, + tokenInputs: { ...prevState.tokenInputs, [currencySearchInputState]: currency }, + })) + break + default: + break + } + }, + [currencySearchInputState, setPositionState], + ) + + const handleFeeTierSelect = useCallback( + (feeTier: number) => { + setPositionState((prevState) => ({ ...prevState, fee: feeTier })) + }, + [setPositionState], + ) + + const feeTiers = [ + { value: FeeAmount.LOWEST, title: t(`fee.bestForVeryStable`), selectionPercent: 0 }, + { value: FeeAmount.LOW, title: t(`fee.bestForStablePairs`), selectionPercent: 0 }, + { value: FeeAmount.MEDIUM, title: t(`fee.bestForMost`), selectionPercent: 96 }, + { value: FeeAmount.HIGH, title: t(`fee.bestForExotic`), selectionPercent: 4 }, + ] + + return ( + <> + + + + + {tokensLocked ? t('pool.tokenPair') : t('pool.selectPair')} + + {tokensLocked ? t('position.migrate.liquidity') : t('position.provide.liquidity')} + + + {tokensLocked && token0 && token1 ? ( + + + + + {token0.symbol} / {token1.symbol} + + + + ) : ( + + setCurrencySearchInputState(PositionField.TOKEN0)} /> + setCurrencySearchInputState(PositionField.TOKEN1)} /> + + )} + {protocolVersion === ProtocolVersion.V4 && } + + + + + + + + + {protocolVersion === ProtocolVersion.V2 ? t('fee.tier.description.v2') : t('fee.tier.description')} + + + + {protocolVersion !== ProtocolVersion.V2 && ( + + + + + + + + + + + + {fee === FeeAmount.MEDIUM ? ( + + + + ) : feeTiers.find((tier) => tier.value === fee) ? null : ( + + + + )} + + + + {isShowMoreFeeTiersEnabled && ( + + {feeTiers.map((feeTier) => ( + + ))} + + )} + {protocolVersion === ProtocolVersion.V4 && ( + { + setFeeTierSearchModalOpen(true) + }} + /> + )} + + )} + + + + setCurrencySearchInputState(undefined)} + onCurrencySelect={handleCurrencySelect} + /> + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/hooks.tsx b/apps/web/src/pages/Pool/Positions/create/hooks.tsx new file mode 100644 index 00000000000..c03b3d33ebb --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/hooks.tsx @@ -0,0 +1,28 @@ +import { useAccount } from 'hooks/useAccount' +import { PositionInfo, PositionState } from 'pages/Pool/Positions/create/types' +import { useMemo } from 'react' +import { useGetPoolsByTokens } from 'uniswap/src/data/rest/getPools' +import { UniverseChainId } from 'uniswap/src/types/chains' + +export function useDerivedPositionInfo(state: PositionState): PositionInfo { + const { chainId } = useAccount() + const tokens = [ + state.tokenInputs.TOKEN0?.isNative ? state.tokenInputs.TOKEN0.wrapped : state.tokenInputs.TOKEN0, + state.tokenInputs.TOKEN1?.isNative ? state.tokenInputs.TOKEN1.wrapped : state.tokenInputs.TOKEN1, + ] + const sortedTokens = tokens.sort((a, b) => (!b ? -1 : a?.sortsBefore(b) ? -1 : 1)) + const { data } = useGetPoolsByTokens({ + fee: state.fee, + chainId: chainId ?? (UniverseChainId.Mainnet as number), + protocolVersions: [state.protocolVersion], + token0: sortedTokens[0]?.address, + token1: sortedTokens[1]?.address, + }) + + return useMemo( + () => ({ + pool: data?.pools?.[0], + }), + [data?.pools], + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/shared.tsx b/apps/web/src/pages/Pool/Positions/create/shared.tsx new file mode 100644 index 00000000000..a2ef7a86030 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/shared.tsx @@ -0,0 +1,50 @@ +import { Flex, GeneratedIcon, Text, styled } from 'ui/src' +import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled' +import { iconSizes } from 'ui/src/theme' +import { useTranslation } from 'uniswap/src/i18n' + +export const Container = styled(Flex, { + gap: 32, + p: '$spacing24', + borderRadius: '$rounded20', + borderWidth: '$spacing1', + borderColor: '$surface3', + maxWidth: 580, + overflow: 'hidden', +}) + +export function AdvancedButton({ + title, + tooltip = true, + Icon, + onPress, +}: { + title: string + tooltip?: boolean + Icon: GeneratedIcon + onPress: () => void +}) { + const { t } = useTranslation() + return ( + + + + + {title} + + + + ({t('common.advanced')}) + + {/* TODO(WEB-4920): implement tooltip text here */} + {tooltip && } + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/types.ts b/apps/web/src/pages/Pool/Positions/create/types.ts new file mode 100644 index 00000000000..f38fc022522 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/types.ts @@ -0,0 +1,51 @@ +// eslint-disable-next-line no-restricted-imports +import { Pool, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Currency } from '@uniswap/sdk-core' +import { Dispatch, SetStateAction } from 'react' +import { PositionField } from 'types/position' + +export enum PositionFlowStep { + SELECT_TOKENS_AND_FEE_TIER, + PRICE_RANGE, + DEPOSIT, +} + +export interface PositionState { + protocolVersion: ProtocolVersion + tokenInputs: { [field in PositionField]?: Currency } + fee: number + hook?: string +} + +export const DEFAULT_POSITION_STATE: PositionState = { + tokenInputs: {}, + fee: 3000, + hook: undefined, + protocolVersion: ProtocolVersion.UNSPECIFIED, +} + +export interface PositionInfo { + pool?: Pool +} + +export type CreatePositionContextType = { + step: PositionFlowStep + setStep: Dispatch> + positionState: PositionState + setPositionState: Dispatch> + derivedPositionInfo: PositionInfo + feeTierSearchModalOpen: boolean + setFeeTierSearchModalOpen: Dispatch> +} + +export interface PriceRangeState { + priceInverted: boolean + fullRange: boolean + minPrice: string + maxPrice: string +} + +export type PriceRangeContextType = { + priceRangeState: PriceRangeState + setPriceRangeState: Dispatch> +} diff --git a/apps/web/src/pages/Pool/Positions/index.tsx b/apps/web/src/pages/Pool/Positions/index.tsx index a0fb4f53251..c3f2e4a3d21 100644 --- a/apps/web/src/pages/Pool/Positions/index.tsx +++ b/apps/web/src/pages/Pool/Positions/index.tsx @@ -1,13 +1,15 @@ /* eslint-disable-next-line no-restricted-imports */ -import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Position, PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { LiquidityPositionCard } from 'components/Liquidity/LiquidityPositionCard' import { useAccount } from 'hooks/useAccount' import { PositionsHeader } from 'pages/Pool/Positions/PositionsHeader' -import { useState } from 'react' +import { useCallback, useState } from 'react' +import { useNavigate } from 'react-router-dom' import { ClickableTamaguiStyle } from 'theme/components' import { Flex } from 'ui/src' import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' import { UniverseChainId } from 'uniswap/src/types/chains' +import { logger } from 'utilities/src/logger/logger' export default function Positions() { const [chainFilter, setChainFilter] = useState(null) @@ -22,16 +24,31 @@ export default function Positions() { PositionStatus.CLOSED, ]) + const navigate = useNavigate() const account = useAccount() const { address } = account const { data } = useGetPositionsQuery({ - chainIds: chainFilter ? [chainFilter] : undefined, - protocolVersions: versionFilter, - positionStatuses: statusFilter, address, }) + const onNavigateToPosition = useCallback( + (position: Position) => { + if (position.position.case === 'v2Pair' && position.position.value.token0 && position.position.value.token1) { + navigate(`/positions/v2/${position.position.value.token0.address}/${position.position.value.token1.address}`) + } else if (position.position.case === 'v3Position') { + navigate(`/positions/v3/${position.position.value.tokenId}`) + } else if (position.position.case === 'v4Position') { + navigate(`/positions/v4/${position.position.value.poolPosition?.tokenId}`) + } else { + logger.error('Invalid position', { + tags: { file: 'Positions/index.tsx', function: 'onPress' }, + }) + } + }, + [navigate], + ) + // TODO(WEB-4920): implement pagination w/ max 8 positions per page. return ( @@ -65,9 +82,7 @@ export default function Positions() { key={index} liquidityPosition={position} {...ClickableTamaguiStyle} - onPress={() => { - // TODO(WEB-4920): navigate to the PosDP for this position - }} + onPress={() => onNavigateToPosition(position)} /> ) })} diff --git a/apps/web/src/pages/RemoveLiquidity/index.tsx b/apps/web/src/pages/RemoveLiquidity/index.tsx index 72a51b10496..f1f91aeec0e 100644 --- a/apps/web/src/pages/RemoveLiquidity/index.tsx +++ b/apps/web/src/pages/RemoveLiquidity/index.tsx @@ -15,6 +15,7 @@ import { BlueCard, LightCard } from 'components/Card/cards' import CurrencyInputPanel from 'components/CurrencyInputPanel' import CurrencyLogo from 'components/Logo/CurrencyLogo' import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { AddRemoveTabs } from 'components/NavigationTabs' import { MinimalPositionCard } from 'components/PositionCard' import Slider from 'components/Slider' @@ -48,8 +49,6 @@ import { useUserSlippageToleranceWithDefault } from 'state/user/hooks' import { StyledInternalLink, ThemedText } from 'theme/components' import { Text } from 'ui/src' import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { Trans } from 'uniswap/src/i18n' @@ -503,10 +502,6 @@ function RemoveLiquidity() { liquidityPercentChangeCallback, ) - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - if (!networkSupportsV2) { return } @@ -715,13 +710,7 @@ function RemoveLiquidity() { element={InterfaceElementName.CONNECT_WALLET_BUTTON} > - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) : ( diff --git a/apps/web/src/pages/RouteDefinitions.tsx b/apps/web/src/pages/RouteDefinitions.tsx index 0b11a79b5da..43eeb2f5dd2 100644 --- a/apps/web/src/pages/RouteDefinitions.tsx +++ b/apps/web/src/pages/RouteDefinitions.tsx @@ -8,7 +8,7 @@ import { t } from 'uniswap/src/i18n' import { isBrowserRouterEnabled } from 'utils/env' // High-traffic pages (index and /swap) should not be lazy-loaded. import Landing from 'pages/Landing' -import { NewPosition } from 'pages/LegacyPool/NewPosition' +import { CreatePosition } from 'pages/Pool/Positions/create/CreatePosition' import Swap from 'pages/Swap' const NftExplore = lazy(() => import('nft/pages/explore')) @@ -25,6 +25,8 @@ const NotFound = lazy(() => import('pages/NotFound')) const Pool = lazy(() => import('pages/Pool')) const LegacyPool = lazy(() => import('pages/LegacyPool')) const LegacyPositionPage = lazy(() => import('pages/LegacyPool/PositionPage')) +const PositionPage = lazy(() => import('pages/Pool/Positions/PositionPage')) +const V2PositionPage = lazy(() => import('pages/Pool/Positions/V2PositionPage')) const LegacyPoolV2 = lazy(() => import('pages/LegacyPool/v2')) const PoolDetails = lazy(() => import('pages/PoolDetails')) const PoolFinder = lazy(() => import('pages/PoolFinder')) @@ -202,8 +204,14 @@ export const routes: RouteDefinition[] = [ }), // Refreshed pool routes createRouteDefinition({ - path: '/positions/new', - getElement: () => , + path: '/positions/create', + getElement: () => , + getTitle: getPositionPageTitle, + getDescription: getPositionPageDescription, + }), + createRouteDefinition({ + path: '/positions/create/:protocolVersion', + getElement: () => , getTitle: getPositionPageTitle, getDescription: getPositionPageDescription, }), @@ -213,6 +221,24 @@ export const routes: RouteDefinition[] = [ getTitle: getPositionPageTitle, getDescription: getPositionPageDescription, }), + createRouteDefinition({ + path: '/positions/v2/:currencyIdA/:currencyIdB', + getElement: () => , + getTitle: getPositionPageTitle, + getDescription: getPositionPageDescription, + }), + createRouteDefinition({ + path: '/positions/v3/:tokenId', + getElement: () => , + getTitle: getPositionPageTitle, + getDescription: getPositionPageDescription, + }), + createRouteDefinition({ + path: '/positions/v4/:tokenId', + getElement: () => , + getTitle: getPositionPageTitle, + getDescription: getPositionPageDescription, + }), createRouteDefinition({ path: '/migrate/v3/:positionId', // Position token ID (v3) getElement: () => , diff --git a/apps/web/src/pages/Swap/Buy/BuyFormButton.tsx b/apps/web/src/pages/Swap/Buy/BuyFormButton.tsx index a28788d2860..02a3140b983 100644 --- a/apps/web/src/pages/Swap/Buy/BuyFormButton.tsx +++ b/apps/web/src/pages/Swap/Buy/BuyFormButton.tsx @@ -1,11 +1,10 @@ import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' import { ButtonLight } from 'components/Button/buttons' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { useBuyFormContext } from 'pages/Swap/Buy/BuyFormContext' import { Button, Flex, SpinningLoader, Text, WidthAnimator } from 'ui/src' import { iconSizes } from 'ui/src/theme' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' -import { Trans, useTranslation } from 'uniswap/src/i18n' +import { useTranslation } from 'uniswap/src/i18n' import { useAccount } from 'wagmi' interface BuyFormButtonProps { @@ -21,20 +20,10 @@ export function BuyFormButton({ forceDisabled }: BuyFormButtonProps) { const { inputAmount } = buyFormState const { notAvailableInThisRegion, quotes, fetchingQuotes, error } = derivedBuyFormInfo - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - if (!account.isConnected) { return ( - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) } diff --git a/apps/web/src/pages/Swap/Buy/CountryListModal.tsx b/apps/web/src/pages/Swap/Buy/CountryListModal.tsx index 6401f678ef6..c43160e22d3 100644 --- a/apps/web/src/pages/Swap/Buy/CountryListModal.tsx +++ b/apps/web/src/pages/Swap/Buy/CountryListModal.tsx @@ -5,12 +5,12 @@ import { ContentWrapper } from 'pages/Swap/Buy/shared' import { ChangeEvent, useCallback, useMemo, useRef, useState } from 'react' import AutoSizer from 'react-virtualized-auto-sizer' import { FixedSizeList } from 'react-window' -import { NAV_HEIGHT } from 'theme' import { CloseIcon } from 'theme/components' import { AdaptiveWebModal, Flex, styled } from 'ui/src' import { Text } from 'ui/src/components/text/Text' import { FORCountry } from 'uniswap/src/features/fiatOnRamp/types' import { useTranslation } from 'uniswap/src/i18n' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' import { bubbleToTop } from 'utilities/src/primitives/array' const ROW_ITEM_SIZE = 56 @@ -67,7 +67,7 @@ export function CountryListModal({ flex={1} onClose={closeModal} maxHeight={700} - $sm={{ height: `calc(100dvh - ${NAV_HEIGHT}px)` }} + $sm={{ height: `calc(100dvh - ${INTERFACE_NAV_HEIGHT}px)` }} > diff --git a/apps/web/src/pages/Swap/Buy/__snapshots__/PredefinedAmount.test.tsx.snap b/apps/web/src/pages/Swap/Buy/__snapshots__/PredefinedAmount.test.tsx.snap index d5abc5106c7..ae19017a0d8 100644 --- a/apps/web/src/pages/Swap/Buy/__snapshots__/PredefinedAmount.test.tsx.snap +++ b/apps/web/src/pages/Swap/Buy/__snapshots__/PredefinedAmount.test.tsx.snap @@ -69,7 +69,7 @@ exports[`PredefinedAmount renders correctly with amount= 300 , currentAmount= "1 $300 @@ -148,7 +148,7 @@ exports[`PredefinedAmount renders correctly with amount= 1000 , currentAmount= " $1000 diff --git a/apps/web/src/pages/Swap/Limit/LimitExpirySection.tsx b/apps/web/src/pages/Swap/Limit/LimitExpirySection.tsx index b055200e1ab..d701f65c822 100644 --- a/apps/web/src/pages/Swap/Limit/LimitExpirySection.tsx +++ b/apps/web/src/pages/Swap/Limit/LimitExpirySection.tsx @@ -29,7 +29,8 @@ const LimitExpiryButton = styled.button<{ $selected: boolean }>` const EXPIRY_OPTIONS = [LimitsExpiry.Day, LimitsExpiry.Week, LimitsExpiry.Month, LimitsExpiry.Year] -function getExpiryLabelText(expiry: LimitsExpiry) { +// eslint-disable-next-line consistent-return +function getExpiryLabelText(expiry: LimitsExpiry): string { switch (expiry) { case LimitsExpiry.Day: return t('common.oneDay') diff --git a/apps/web/src/pages/Swap/Limit/LimitForm.tsx b/apps/web/src/pages/Swap/Limit/LimitForm.tsx index 473c9bdb4a8..c3a7fc40cb2 100644 --- a/apps/web/src/pages/Swap/Limit/LimitForm.tsx +++ b/apps/web/src/pages/Swap/Limit/LimitForm.tsx @@ -12,11 +12,11 @@ import { useCurrentPriceAdjustment, } from 'components/CurrencyInputPanel/LimitPriceInputPanel/useCurrentPriceAdjustment' import SwapCurrencyInputPanel from 'components/CurrencyInputPanel/SwapCurrencyInputPanel' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import Column from 'components/deprecated/Column' import Row from 'components/deprecated/Row' -import { Field } from 'components/swap/constants' import { ArrowContainer, ArrowWrapper, SwapSection } from 'components/swap/styled' -import { getChain, isUniswapXSupportedChain, useIsSupportedChainId } from 'constants/chains' +import { getChain, useIsSupportedChainId, useIsUniswapXSupportedChain } from 'constants/chains' import { ZERO_PERCENT } from 'constants/misc' import { useAccount } from 'hooks/useAccount' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' @@ -37,11 +37,8 @@ import { CurrencyState } from 'state/swap/types' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { Anchor, Text, styled as tamaguiStyled } from 'ui/src' import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled' -import { colors, validColor } from 'ui/src/theme' import { nativeOnChain } from 'uniswap/src/constants/tokens' import { uniswapUrls } from 'uniswap/src/constants/urls' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import { Locale } from 'uniswap/src/features/language/constants' import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementName, InterfacePageNameLocal } from 'uniswap/src/features/telemetry/constants' @@ -100,6 +97,7 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { setCurrencyState, } = useSwapAndLimitContext() const isSupportedChain = useIsSupportedChainId(chainId) + const isUniswapXSupportedChain = useIsUniswapXSupportedChain(chainId) const { limitState, setLimitState, derivedLimitInfo } = useLimitContext() const { currencyBalances, parsedAmounts, parsedLimitPrice, limitOrderTrade, marketPrice } = derivedLimitInfo @@ -173,7 +171,8 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { const onSelectCurrency = useCallback( (type: keyof CurrencyState, newCurrency: Currency) => { if ((type === 'inputCurrency' ? outputCurrency : inputCurrency)?.equals(newCurrency)) { - return switchTokens() + switchTokens() + return } const [newInput, newOutput] = type === 'inputCurrency' ? [newCurrency, outputCurrency] : [inputCurrency, newCurrency] @@ -228,54 +227,56 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { }, []) const maxInputAmount: CurrencyAmount | undefined = useMemo( - () => maxAmountSpend(currencyBalances[Field.INPUT]), + () => maxAmountSpend(currencyBalances[CurrencyField.INPUT]), [currencyBalances], ) - const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount)) + const showMaxButton = Boolean( + maxInputAmount?.greaterThan(0) && !parsedAmounts[CurrencyField.INPUT]?.equalTo(maxInputAmount), + ) const handleMaxInput = useCallback(() => { maxInputAmount && onTypeInput('inputAmount')(maxInputAmount.toExact()) }, [maxInputAmount, onTypeInput]) const hasInsufficientFunds = - parsedAmounts.INPUT && currencyBalances.INPUT ? currencyBalances.INPUT.lessThan(parsedAmounts.INPUT) : false + parsedAmounts.input && currencyBalances.input ? currencyBalances.input.lessThan(parsedAmounts.input) : false const allowance = usePermit2Allowance( - parsedAmounts.INPUT?.currency?.isNative ? undefined : (parsedAmounts.INPUT as CurrencyAmount), + parsedAmounts.input?.currency?.isNative ? undefined : (parsedAmounts.input as CurrencyAmount), isSupportedChain ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined, TradeFillType.UniswapX, ) - const fiatValueTradeInput = useUSDPrice(parsedAmounts.INPUT) - const fiatValueTradeOutput = useUSDPrice(parsedAmounts.OUTPUT) + const fiatValueTradeInput = useUSDPrice(parsedAmounts.input) + const fiatValueTradeOutput = useUSDPrice(parsedAmounts.output) const formattedAmounts = useMemo(() => { // if there is no Price field, then just default to user-typed amounts if (!limitState.limitPrice) { return { - [Field.INPUT]: limitState.inputAmount, - [Field.OUTPUT]: limitState.outputAmount, + [CurrencyField.INPUT]: limitState.inputAmount, + [CurrencyField.OUTPUT]: limitState.outputAmount, } } const formattedInput = limitState.isInputAmountFixed ? limitState.inputAmount : formatCurrencyAmount({ - amount: derivedLimitInfo.parsedAmounts[Field.INPUT], + amount: derivedLimitInfo.parsedAmounts[CurrencyField.INPUT], type: NumberType.SwapTradeAmount, placeholder: '', }) const formattedOutput = limitState.isInputAmountFixed ? formatCurrencyAmount({ - amount: derivedLimitInfo.parsedAmounts[Field.OUTPUT], + amount: derivedLimitInfo.parsedAmounts[CurrencyField.OUTPUT], type: NumberType.SwapTradeAmount, placeholder: '', }) : limitState.outputAmount return { - [Field.INPUT]: formattedInput, - [Field.OUTPUT]: formattedOutput, + [CurrencyField.INPUT]: formattedInput, + [CurrencyField.OUTPUT]: formattedOutput, } }, [ limitState.limitPrice, @@ -318,7 +319,7 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { } - value={formattedAmounts[Field.INPUT]} + value={formattedAmounts[CurrencyField.INPUT]} showMaxButton={showMaxButton} currency={inputCurrency ?? null} currencyField={CurrencyField.INPUT} @@ -345,7 +346,7 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { } - value={formattedAmounts[Field.OUTPUT]} + value={formattedAmounts[CurrencyField.OUTPUT]} showMaxButton={false} currency={outputCurrency ?? null} currencyField={CurrencyField.OUTPUT} @@ -366,7 +367,7 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { hasInsufficientFunds={hasInsufficientFunds} limitPriceError={priceError} /> - {isUniswapXSupportedChain(chainId) && !!priceError && inputCurrency && outputCurrency && limitOrderTrade && ( + {isUniswapXSupportedChain && !!priceError && inputCurrency && outputCurrency && limitOrderTrade && ( )} - + - {!isUniswapXSupportedChain(chainId) ? ( + {!isUniswapXSupportedChain ? ( { - onSelectCurrency(field === Field.INPUT ? 'inputCurrency' : 'outputCurrency', currency) + onCurrencySelection={(field: CurrencyField, currency) => { + onSelectCurrency(field === CurrencyField.INPUT ? 'inputCurrency' : 'outputCurrency', currency) }} onConfirm={handleSubmit} onDismiss={() => { @@ -465,11 +466,7 @@ function SubmitOrderButton({ const account = useAccount() const { chainId } = useSwapAndLimitContext() - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - - if (!isUniswapXSupportedChain(chainId)) { + if (!useIsUniswapXSupportedChain(chainId)) { return ( @@ -480,13 +477,7 @@ function SubmitOrderButton({ if (!account.isConnected) { return ( - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) } @@ -513,7 +504,7 @@ function SubmitOrderButton({ data-testid="submit-order-button" disabled={!trade || !!limitPriceError} > - + diff --git a/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx b/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx index b99403d0a3b..03659cd04c3 100644 --- a/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx +++ b/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx @@ -10,7 +10,7 @@ import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal' import Column from 'components/deprecated/Column' import Row, { RowBetween } from 'components/deprecated/Row' import { getChain, useSupportedChainId } from 'constants/chains' -import { PrefetchBalancesWrapper } from 'graphql/data/apollo/TokenBalancesProvider' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useActiveLocalCurrency, useActiveLocalCurrencyComponents } from 'hooks/useActiveLocalCurrency' import { useUSDPrice } from 'hooks/useUSDPrice' import styled, { css } from 'lib/styled-components' diff --git a/apps/web/src/pages/Swap/Send/SendForm.tsx b/apps/web/src/pages/Swap/Send/SendForm.tsx index 11f1f39ccc5..17a3e7ec3f0 100644 --- a/apps/web/src/pages/Swap/Send/SendForm.tsx +++ b/apps/web/src/pages/Swap/Send/SendForm.tsx @@ -1,6 +1,7 @@ import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events' import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' import { ButtonLight, ButtonPrimary } from 'components/Button/buttons' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import Column from 'components/deprecated/Column' import { useIsSupportedChainId } from 'constants/chains' import { useAccount } from 'hooks/useAccount' @@ -17,8 +18,6 @@ import { SendContextProvider, useSendContext } from 'state/send/SendContext' import { CurrencyState } from 'state/swap/types' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { InterfacePageNameLocal } from 'uniswap/src/features/telemetry/constants' import { Trans } from 'uniswap/src/i18n' @@ -187,10 +186,6 @@ function SendFormInner({ disableTokenInputs = false, onCurrencyChange }: SendFor .catch(() => undefined) }, [handleModalState, sendCallback, setSendState]) - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - return ( <> @@ -203,13 +198,7 @@ function SendFormInner({ disableTokenInputs = false, onCurrencyChange }: SendFor element={InterfaceElementName.CONNECT_WALLET_BUTTON} > - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) : !multichainUXEnabled && initialChainId && initialChainId !== account.chainId ? ( diff --git a/apps/web/src/pages/Swap/Send/__snapshots__/SendCurrencyInputForm.test.tsx.snap b/apps/web/src/pages/Swap/Send/__snapshots__/SendCurrencyInputForm.test.tsx.snap index 62f8e2aa521..019b8f55a53 100644 --- a/apps/web/src/pages/Swap/Send/__snapshots__/SendCurrencyInputForm.test.tsx.snap +++ b/apps/web/src/pages/Swap/Send/__snapshots__/SendCurrencyInputForm.test.tsx.snap @@ -13,7 +13,7 @@ exports[`SendCurrencyInputform renders input in fiat correctly 1`] = ` } .c23 img { - width: 18px; + width: 17px; height: 36px; object-fit: cover; } @@ -584,7 +584,7 @@ exports[`SendCurrencyInputform renders input in token amount correctly 1`] = ` } .c22 img { - width: 18px; + width: 17px; height: 36px; object-fit: cover; } @@ -1139,7 +1139,7 @@ exports[`SendCurrencyInputform should render placeholder values 1`] = ` } .c23 img { - width: 18px; + width: 17px; height: 36px; object-fit: cover; } diff --git a/apps/web/src/pages/Swap/Send/__snapshots__/SendRecipientForm.test.tsx.snap b/apps/web/src/pages/Swap/Send/__snapshots__/SendRecipientForm.test.tsx.snap index 83fa766cead..845168c55f2 100644 --- a/apps/web/src/pages/Swap/Send/__snapshots__/SendRecipientForm.test.tsx.snap +++ b/apps/web/src/pages/Swap/Send/__snapshots__/SendRecipientForm.test.tsx.snap @@ -351,7 +351,7 @@ exports[`SendCurrencyInputform should render correctly with unitag 1`] = `
diff --git a/apps/web/src/pages/Swap/SwapForm.tsx b/apps/web/src/pages/Swap/SwapForm.tsx index 5d7dc92fca1..63077f938d9 100644 --- a/apps/web/src/pages/Swap/SwapForm.tsx +++ b/apps/web/src/pages/Swap/SwapForm.tsx @@ -3,6 +3,7 @@ import { InterfaceEventName, InterfaceSectionName, SwapEventName, + SwapPriceImpactUserResponse, } from '@uniswap/analytics-events' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' @@ -12,13 +13,13 @@ import { GrayCard } from 'components/Card/cards' import { ConfirmSwapModal } from 'components/ConfirmSwapModal' import SwapCurrencyInputPanel from 'components/CurrencyInputPanel/SwapCurrencyInputPanel' import ErrorIcon from 'components/Icons/Error' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import Column, { AutoColumn } from 'components/deprecated/Column' import Row from 'components/deprecated/Row' import PriceImpactModal from 'components/swap/PriceImpactModal' import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown' import confirmPriceImpactWithoutFee from 'components/swap/confirmPriceImpactWithoutFee' -import { Field } from 'components/swap/constants' import { ArrowContainer, ArrowWrapper, OutputSwapSection, SwapSection } from 'components/swap/styled' import { useIsSupportedChainId, useSupportedChainId } from 'constants/chains' import { useCurrencyInfo } from 'hooks/Tokens' @@ -52,8 +53,6 @@ import { Text } from 'ui/src' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' @@ -137,10 +136,10 @@ export function SwapForm({ useEffect(() => { // Force exact input if the user switches to an output token with tax - if (outputTokenHasTax && independentField === Field.OUTPUT) { + if (outputTokenHasTax && independentField === CurrencyField.OUTPUT) { setSwapState((state) => ({ ...state, - independentField: Field.INPUT, + independentField: CurrencyField.INPUT, typedValue: '', })) } @@ -150,39 +149,39 @@ export function SwapForm({ wrapType, execute: onWrap, inputError: wrapInputError, - } = useWrapCallback(currencies[Field.INPUT], currencies[Field.OUTPUT], typedValue) + } = useWrapCallback(currencies[CurrencyField.INPUT], currencies[CurrencyField.OUTPUT], typedValue) const showWrap: boolean = wrapType !== WrapType.NotApplicable const parsedAmounts = useMemo( () => showWrap ? { - [Field.INPUT]: parsedAmount, - [Field.OUTPUT]: parsedAmount, + [CurrencyField.INPUT]: parsedAmount, + [CurrencyField.OUTPUT]: parsedAmount, } : { - [Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount, - [Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount, + [CurrencyField.INPUT]: independentField === CurrencyField.INPUT ? parsedAmount : trade?.inputAmount, + [CurrencyField.OUTPUT]: independentField === CurrencyField.OUTPUT ? parsedAmount : trade?.outputAmount, }, [independentField, parsedAmount, showWrap, trade], ) - const showFiatValueInput = Boolean(parsedAmounts[Field.INPUT]) - const showFiatValueOutput = Boolean(parsedAmounts[Field.OUTPUT]) + const showFiatValueInput = Boolean(parsedAmounts[CurrencyField.INPUT]) + const showFiatValueOutput = Boolean(parsedAmounts[CurrencyField.OUTPUT]) const getSingleUnitAmount = (currency?: Currency) => { if (!currency) { - return + return undefined } return CurrencyAmount.fromRawAmount(currency, JSBI.BigInt(10 ** currency.decimals)) } const fiatValueInput = useUSDPrice( - parsedAmounts[Field.INPUT] ?? getSingleUnitAmount(currencies[Field.INPUT]), - currencies[Field.INPUT], + parsedAmounts[CurrencyField.INPUT] ?? getSingleUnitAmount(currencies[CurrencyField.INPUT]), + currencies[CurrencyField.INPUT], ) const fiatValueOutput = useUSDPrice( - parsedAmounts[Field.OUTPUT] ?? getSingleUnitAmount(currencies[Field.OUTPUT]), - currencies[Field.OUTPUT], + parsedAmounts[CurrencyField.OUTPUT] ?? getSingleUnitAmount(currencies[CurrencyField.OUTPUT]), + currencies[CurrencyField.OUTPUT], ) const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo( @@ -209,25 +208,26 @@ export function SwapForm({ ) const { onSwitchTokens, onCurrencySelection, onUserInput } = useSwapActionHandlers() - const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT + const dependentField: CurrencyField = + independentField === CurrencyField.INPUT ? CurrencyField.OUTPUT : CurrencyField.INPUT const handleTypeInput = useCallback( (value: string) => { - onUserInput(Field.INPUT, value) + onUserInput(CurrencyField.INPUT, value) maybeLogFirstSwapAction(trace) }, [onUserInput, trace], ) const handleTypeOutput = useCallback( (value: string) => { - onUserInput(Field.OUTPUT, value) + onUserInput(CurrencyField.OUTPUT, value) maybeLogFirstSwapAction(trace) }, [onUserInput, trace], ) const navigate = useNavigate() - const swapIsUnsupported = useIsSwapUnsupported(currencies[Field.INPUT], currencies[Field.OUTPUT]) + const swapIsUnsupported = useIsSwapUnsupported(currencies[CurrencyField.INPUT], currencies[CurrencyField.OUTPUT]) const isLandingPage = useIsLandingPage() const navigateToSwapWithParams = useCallback(() => { @@ -248,12 +248,6 @@ export function SwapForm({ swapState.typedValue, ]) - // reset if they close warning without tokens in params - const handleDismissTokenWarning = useCallback(() => { - setDismissTokenWarning(true) - navigate('/swap') - }, [navigate]) - // modal and loading const [{ showConfirm, tradeToConfirm, swapError, swapResult }, setSwapFormState] = useState<{ showConfirm: boolean @@ -316,24 +310,28 @@ export function SwapForm({ const selectChain = useSelectChain() const userHasSpecifiedInputOutput = Boolean( - currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0)), + currencies[CurrencyField.INPUT] && + currencies[CurrencyField.OUTPUT] && + parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0)), ) const maximumAmountIn = useMaxAmountIn(trade, allowedSlippage) const allowance = usePermit2Allowance( maximumAmountIn ?? - (parsedAmounts[Field.INPUT]?.currency.isToken - ? (parsedAmounts[Field.INPUT] as CurrencyAmount) + (parsedAmounts[CurrencyField.INPUT]?.currency.isToken + ? (parsedAmounts[CurrencyField.INPUT] as CurrencyAmount) : undefined), supportedChainId ? UNIVERSAL_ROUTER_ADDRESS(supportedChainId) : undefined, trade?.fillType, ) const maxInputAmount: CurrencyAmount | undefined = useMemo( - () => maxAmountSpend(currencyBalances[Field.INPUT]), + () => maxAmountSpend(currencyBalances[CurrencyField.INPUT]), [currencyBalances], ) - const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount)) + const showMaxButton = Boolean( + maxInputAmount?.greaterThan(0) && !parsedAmounts[CurrencyField.INPUT]?.equalTo(maxInputAmount), + ) const swapFiatValues = useMemo(() => { return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data, feeUsd: outputFeeFiatValue } }, [fiatValueTradeInput.data, fiatValueTradeOutput.data, outputFeeFiatValue]) @@ -405,13 +403,13 @@ export function SwapForm({ swapError: undefined, txHash, })) - onUserInput(Field.INPUT, '') + onUserInput(CurrencyField.INPUT, '') } catch (error) { if (!didUserReject(error)) { sendAnalyticsEvent(SwapEventName.SWAP_ERROR, { wrapType, - input: currencies[Field.INPUT], - output: currencies[Field.OUTPUT], + input: currencies[CurrencyField.INPUT], + output: currencies[CurrencyField.OUTPUT], }) } else { logger.debug('SwapForm', 'handleOnWrap', 'rejected wrap/unwrap') @@ -444,7 +442,7 @@ export function SwapForm({ setSwapState((state) => ({ ...state, routerPreferenceOverride: undefined })) // If there was a swap, we want to clear the input if (swapResult) { - onUserInput(Field.INPUT, '') + onUserInput(CurrencyField.INPUT, '') } }, [onUserInput, setSwapState, swapResult]) @@ -454,7 +452,7 @@ export function SwapForm({ const handleInputSelect = useCallback( (inputCurrency: Currency) => { - onCurrencySelection(Field.INPUT, inputCurrency) + onCurrencySelection(CurrencyField.INPUT, inputCurrency) onCurrencyChange?.({ inputCurrency, outputCurrency: currencyState.outputCurrency, @@ -466,13 +464,13 @@ export function SwapForm({ const inputCurrencyNumericalInputRef = useRef(null) const handleMaxInput = useCallback(() => { - maxInputAmount && onUserInput(Field.INPUT, maxInputAmount.toExact()) + maxInputAmount && onUserInput(CurrencyField.INPUT, maxInputAmount.toExact()) maybeLogFirstSwapAction(trace) }, [maxInputAmount, onUserInput, trace]) const handleOutputSelect = useCallback( (outputCurrency: Currency) => { - onCurrencySelection(Field.OUTPUT, outputCurrency) + onCurrencySelection(CurrencyField.OUTPUT, outputCurrency) onCurrencyChange?.({ inputCurrency: currencyState.inputCurrency, outputCurrency, @@ -500,7 +498,7 @@ export function SwapForm({ !isLandingPage && !showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing), ) - const inputCurrency = currencies[Field.INPUT] ?? undefined + const inputCurrency = currencies[CurrencyField.INPUT] ?? undefined const switchingChain = useAppSelector((state) => state.wallets.switchingChain) const targetChain = switchingChain ? switchingChain : undefined @@ -508,18 +506,23 @@ export function SwapForm({ // @ts-ignore const isUsingBlockedExtension = window.ethereum?.['isPocketUniverseZ'] - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - return ( <> 0 && !dismissTokenWarning} token0={urlTokensNotInDefault[0]} token1={urlTokensNotInDefault[1]} - onContinue={handleConfirmTokenWarning} - onCancel={handleDismissTokenWarning} + onAcknowledge={handleConfirmTokenWarning} + onReject={() => { + setDismissTokenWarning(true) + onCurrencySelection(CurrencyField.INPUT, undefined) + onCurrencySelection(CurrencyField.OUTPUT, undefined) + }} + closeModalOnly={() => { + setDismissTokenWarning(true) + }} + onToken0BlockAcknowledged={() => onCurrencySelection(CurrencyField.INPUT, undefined)} + onToken1BlockAcknowledged={() => onCurrencySelection(CurrencyField.OUTPUT, undefined)} showCancel={true} /> {trade && showConfirm && ( @@ -552,8 +555,16 @@ export function SwapForm({ {showPriceImpactModal && showPriceImpactWarning && ( setShowPriceImpactModal(false)} + onDismiss={() => { + sendAnalyticsEvent(SwapEventName.SWAP_PRICE_IMPACT_ACKNOWLEDGED, { + response: SwapPriceImpactUserResponse.REJECTED, + }) + setShowPriceImpactModal(false) + }} onContinue={() => { + sendAnalyticsEvent(SwapEventName.SWAP_PRICE_IMPACT_ACKNOWLEDGED, { + response: SwapPriceImpactUserResponse.ACCEPTED, + }) setShowPriceImpactModal(false) handleContinueToReview() }} @@ -565,17 +576,17 @@ export function SwapForm({ } disabled={disableTokenInputs} - value={formattedAmounts[Field.INPUT]} + value={formattedAmounts[CurrencyField.INPUT]} showMaxButton={showMaxButton} - currency={currencies[Field.INPUT] ?? null} + currency={currencies[CurrencyField.INPUT] ?? null} currencyField={CurrencyField.INPUT} onUserInput={handleTypeInput} onMax={handleMaxInput} fiatValue={showFiatValueInput ? fiatValueInput : undefined} onCurrencySelect={handleInputSelect} - otherCurrency={currencies[Field.OUTPUT]} + otherCurrency={currencies[CurrencyField.OUTPUT]} id={InterfaceSectionName.CURRENCY_INPUT_PANEL} - loading={independentField === Field.OUTPUT && routeIsSyncing} + loading={independentField === CurrencyField.OUTPUT && routeIsSyncing} initialCurrencyLoading={initialCurrencyLoading} ref={inputCurrencyNumericalInputRef} /> @@ -611,7 +622,7 @@ export function SwapForm({ } @@ -619,12 +630,12 @@ export function SwapForm({ hideBalance={false} fiatValue={showFiatValueOutput ? fiatValueOutput : undefined} priceImpact={stablecoinPriceImpact} - currency={currencies[Field.OUTPUT] ?? null} + currency={currencies[CurrencyField.OUTPUT] ?? null} currencyField={CurrencyField.OUTPUT} onCurrencySelect={handleOutputSelect} - otherCurrency={currencies[Field.INPUT]} + otherCurrency={currencies[CurrencyField.INPUT]} id={InterfaceSectionName.CURRENCY_OUTPUT_PANEL} - loading={independentField === Field.INPUT && routeIsSyncing} + loading={independentField === CurrencyField.INPUT && routeIsSyncing} numericalInputSettings={{ // We disable numerical input here if the selected token has tax, since we cannot guarantee exact_outputs for FOT tokens disabled: inputTokenHasTax || outputTokenHasTax, @@ -632,7 +643,7 @@ export function SwapForm({ onDisabledClick: () => inputCurrencyNumericalInputRef.current?.focus(), disabledTooltipBody: ( ), }} @@ -676,13 +687,7 @@ export function SwapForm({ element={InterfaceElementName.CONNECT_WALLET_BUTTON} > - {isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - )} + ) : !multichainUXEnabled && initialChainId && initialChainId !== connectedChainId ? ( diff --git a/apps/web/src/pages/Swap/index.tsx b/apps/web/src/pages/Swap/index.tsx index fc3a402259b..e6e015c103b 100644 --- a/apps/web/src/pages/Swap/index.tsx +++ b/apps/web/src/pages/Swap/index.tsx @@ -3,8 +3,8 @@ import { Currency } from '@uniswap/sdk-core' import { NetworkAlert } from 'components/NetworkAlert' import { SwitchLocaleLink } from 'components/SwitchLocaleLink' import SwapHeader from 'components/swap/SwapHeader' -import { Field } from 'components/swap/constants' import { PageWrapper, SwapWrapper } from 'components/swap/styled' +import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useScreenSize } from 'hooks/screenSize/useScreenSize' import { BuyForm } from 'pages/Swap/Buy/BuyForm' import { LimitFormWrapper } from 'pages/Swap/Limit/LimitForm' @@ -14,6 +14,8 @@ import { ReactNode, useState } from 'react' import { useLocation } from 'react-router-dom' import { InterfaceTrade, TradeState } from 'state/routing/types' import { isPreviewTrade } from 'state/routing/utils' +import { useSwapCallback } from 'state/sagas/transactions/swapSaga' +import { useWrapCallback } from 'state/sagas/transactions/wrapSaga' import { SwapAndLimitContextProvider, SwapContextProvider } from 'state/swap/SwapContext' import { useInitialCurrencyState } from 'state/swap/hooks' import { CurrencyState, SwapAndLimitContext } from 'state/swap/types' @@ -23,13 +25,19 @@ import { AppTFunction } from 'ui/src/i18n/types' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' +import { SwapRedirectFn } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' import { SwapFlow } from 'uniswap/src/features/transactions/swap/SwapFlow' +import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState' import { Deadline } from 'uniswap/src/features/transactions/swap/settings/configs/Deadline' +import { currencyToAsset } from 'uniswap/src/features/transactions/swap/utils/asset' import { useTranslation } from 'uniswap/src/i18n' import { InterfaceChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' import noop from 'utilities/src/react/noop' +const WEB_CUSTOM_SWAP_SETTINGS = [Deadline] + export function getIsReviewableQuote( trade: InterfaceTrade | undefined, tradeState: TradeState, @@ -96,11 +104,13 @@ export function Swap({ initialCurrencyLoading = false, chainId, hideHeader = false, + hideFooter = false, onCurrencyChange, multichainUXEnabled = false, disableTokenInputs = false, compact = false, syncTabToUrl, + swapRedirectCallback, }: { className?: string chainId?: InterfaceChainId @@ -109,16 +119,19 @@ export function Swap({ initialInputCurrency?: Currency initialOutputCurrency?: Currency initialTypedValue?: string - initialIndependentField?: Field + initialIndependentField?: CurrencyField initialCurrencyLoading?: boolean compact?: boolean syncTabToUrl: boolean multichainUXEnabled?: boolean hideHeader?: boolean + hideFooter?: boolean + swapRedirectCallback?: SwapRedirectFn }) { const isDark = useIsDarkMode() const screenSize = useScreenSize() const forAggregatorEnabled = useFeatureFlag(FeatureFlags.ForAggregator) + const universalSwapFlow = useFeatureFlag(FeatureFlags.UniversalSwap) if (universalSwapFlow) { @@ -129,7 +142,17 @@ export function Swap({ initialOutputCurrency={initialOutputCurrency} multichainUXEnabled={multichainUXEnabled} > - + + + ) } @@ -183,36 +206,72 @@ const TAB_TYPE_TO_LABEL = { } function UniversalSwapFlow({ - onCurrencyChange, + hideHeader = false, + hideFooter = false, disableTokenInputs = false, + initialInputCurrency, + initialOutputCurrency, + initialTypedValue, + initialIndependentField, + onCurrencyChange, + swapRedirectCallback, }: { - onCurrencyChange?: (selected: CurrencyState) => void + hideHeader?: boolean + hideFooter?: boolean disableTokenInputs?: boolean + initialInputCurrency?: Currency + initialOutputCurrency?: Currency + initialTypedValue?: string + initialIndependentField?: CurrencyField + onCurrencyChange?: (selected: CurrencyState) => void + swapRedirectCallback?: SwapRedirectFn }) { const [currentTab, setCurrentTab] = useState(SwapTab.Swap) const { t } = useTranslation() const forAggregatorEnabled = useFeatureFlag(FeatureFlags.ForAggregator) + const swapCallback = useSwapCallback() + const wrapCallback = useWrapCallback() + + const input = currencyToAsset(initialInputCurrency) + const output = currencyToAsset(initialOutputCurrency) + + const prefilledState = useSwapPrefilledState({ + input, + output, + exactAmountToken: initialTypedValue ?? '', + exactCurrencyField: initialIndependentField ?? CurrencyField.INPUT, + }) return ( - + - {SWAP_TABS.map((tab) => ( - setCurrentTab(tab)} - > - - {TAB_TYPE_TO_LABEL[tab](t)} - - - ))} + {!hideHeader && + SWAP_TABS.map((tab) => ( + setCurrentTab(tab)} + > + + {TAB_TYPE_TO_LABEL[tab](t)} + + + ))} {currentTab === SwapTab.Swap && ( - + )} {currentTab === SwapTab.Limit && } {currentTab === SwapTab.Send && ( diff --git a/apps/web/src/pages/TokenDetails/index.tsx b/apps/web/src/pages/TokenDetails/index.tsx index b64a65cfd5d..02dd545479c 100644 --- a/apps/web/src/pages/TokenDetails/index.tsx +++ b/apps/web/src/pages/TokenDetails/index.tsx @@ -5,7 +5,7 @@ import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleto import { useChainFromUrlParam } from 'constants/chains' import { useTokenWarning } from 'constants/deprecatedTokenSafety' import { NATIVE_CHAIN_ID, UNKNOWN_TOKEN_SYMBOL } from 'constants/tokens' -import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' +import { useTokenBalancesQuery } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { getSupportedGraphQlChain, gqlToCurrency } from 'graphql/data/util' import { useCurrency } from 'hooks/Tokens' import { useAccount } from 'hooks/useAccount' @@ -123,7 +123,7 @@ function useCreateTDPContext(): PendingTDPContext | LoadedTDPContext { const tokenColor = useSrcColor( extractedColorSrc, - tokenQuery.data?.token?.project?.name ?? tokenQuery.data?.token?.name, + tokenQuery.data?.token?.name ?? tokenQuery.data?.token?.project?.name, theme.surface2, ).tokenColor ?? undefined diff --git a/apps/web/src/pages/__snapshots__/routes.test.ts.snap b/apps/web/src/pages/__snapshots__/routes.test.ts.snap index 553d131803d..18907c51030 100644 --- a/apps/web/src/pages/__snapshots__/routes.test.ts.snap +++ b/apps/web/src/pages/__snapshots__/routes.test.ts.snap @@ -132,7 +132,15 @@ Array [ "getElement": [Function], "getTitle": [Function], "nestedPaths": Array [], - "path": "/positions/new", + "path": "/positions/create", + }, + Object { + "enabled": [Function], + "getDescription": [Function], + "getElement": [Function], + "getTitle": [Function], + "nestedPaths": Array [], + "path": "/positions/create/:protocolVersion", }, Object { "enabled": [Function], @@ -142,6 +150,30 @@ Array [ "nestedPaths": Array [], "path": "/positions", }, + Object { + "enabled": [Function], + "getDescription": [Function], + "getElement": [Function], + "getTitle": [Function], + "nestedPaths": Array [], + "path": "/positions/v2/:currencyIdA/:currencyIdB", + }, + Object { + "enabled": [Function], + "getDescription": [Function], + "getElement": [Function], + "getTitle": [Function], + "nestedPaths": Array [], + "path": "/positions/v3/:tokenId", + }, + Object { + "enabled": [Function], + "getDescription": [Function], + "getElement": [Function], + "getTitle": [Function], + "nestedPaths": Array [], + "path": "/positions/v4/:tokenId", + }, Object { "enabled": [Function], "getDescription": [Function], diff --git a/apps/web/src/pages/paths.ts b/apps/web/src/pages/paths.ts index 1f825b1efa8..bb7eaed7fc7 100644 --- a/apps/web/src/pages/paths.ts +++ b/apps/web/src/pages/paths.ts @@ -30,7 +30,11 @@ export const paths = [ '/pools', '/pools/:tokenId', '/positions', - '/positions/new', + '/positions/create', + '/positions/create/:protocolVersion', + '/positions/v2/:currencyIdA/:currencyIdB', + '/positions/v3/:tokenId', + '/positions/v4/:tokenId', '/add/v2', '/add', '/increase', diff --git a/apps/web/src/setupTests.ts b/apps/web/src/setupTests.ts index 09f764b041e..06b21653492 100644 --- a/apps/web/src/setupTests.ts +++ b/apps/web/src/setupTests.ts @@ -15,6 +15,7 @@ import { Readable } from 'stream' import { toBeVisible } from 'test-utils/matchers' import { mocked } from 'test-utils/mocked' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { mockLocalizationContext } from 'uniswap/src/test/mocks/locale' import { TextDecoder, TextEncoder } from 'util' setupi18n() @@ -82,6 +83,8 @@ jest.mock('@popperjs/core', () => { } }) +jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext({})) + jest.mock('@web3-react/core', () => { const web3React = jest.requireActual('@web3-react/core') const { Empty } = jest.requireActual('@web3-react/empty') diff --git a/apps/web/src/state/activity/polling/orders.ts b/apps/web/src/state/activity/polling/orders.ts index 48e23cc6a3b..06fde5c5823 100644 --- a/apps/web/src/state/activity/polling/orders.ts +++ b/apps/web/src/state/activity/polling/orders.ts @@ -41,6 +41,14 @@ async function fetchLimitStatuses(account: string, orders: UniswapXOrderDetails[ ) } +async function fetchPriorityStatuses(account: string, orders: UniswapXOrderDetails[]): Promise { + return fetchStatuses( + orders, + (order) => order.type === SignatureType.SIGN_PRIORITY_ORDER, + (hashes) => `/orders?swapper=${account}&orderHashes=${hashes}&orderType=${OffchainOrderType.PRIORITY_ORDER}`, + ) +} + async function fetchOrderStatuses(account: string, orders: UniswapXOrderDetails[]): Promise { return fetchStatuses( orders, @@ -77,6 +85,7 @@ export function usePollPendingOrders(onActivityUpdate: OnActivityUpdate) { await Promise.all([ fetchOrderStatuses(account.address, pendingOrders), fetchLimitStatuses(account.address, pendingOrders), + fetchPriorityStatuses(account.address, pendingOrders), ]) ).flat() @@ -126,7 +135,7 @@ export function usePollPendingOrders(onActivityUpdate: OnActivityUpdate) { timeout = setTimeout(getOrderStatuses, currentDelay) return () => clearTimeout(timeout) } - return + return undefined }, [account.address, currentDelay, onActivityUpdate, pendingOrders, provider, realtimeEnabled]) return null diff --git a/apps/web/src/state/activity/polling/transactions.ts b/apps/web/src/state/activity/polling/transactions.ts index 684f191ca64..f0ff95c01e4 100644 --- a/apps/web/src/state/activity/polling/transactions.ts +++ b/apps/web/src/state/activity/polling/transactions.ts @@ -120,7 +120,7 @@ export function usePollPendingTransactions(onActivityUpdate: OnActivityUpdate) { useEffect(() => { if (!account.chainId || !provider || !lastBlockNumber || !hasPending) { - return + return undefined } const cancels = pendingTransactions diff --git a/apps/web/src/state/application/reducer.test.ts b/apps/web/src/state/application/reducer.test.ts index 89f6cd1541d..ea4f30b3afc 100644 --- a/apps/web/src/state/application/reducer.test.ts +++ b/apps/web/src/state/application/reducer.test.ts @@ -3,6 +3,7 @@ import reducer, { addPopup, ApplicationModal, ApplicationState, + PopupType, removePopup, setCloseModal, setOpenModal, @@ -25,27 +26,27 @@ describe('application reducer', () => { describe('popupList', () => { describe('addPopup', () => { it('adds the popup to list with a generated id', () => { - store.dispatch(addPopup({ content: { txn: { hash: 'abc' } } })) + store.dispatch(addPopup({ content: { type: PopupType.Transaction, hash: 'abc' } })) const list = store.getState().popupList expect(list).toEqual([ { key: expect.any(String), show: true, - content: { txn: { hash: 'abc' } }, + content: { type: PopupType.Transaction, hash: 'abc' }, removeAfterMs: 10000, }, ]) }) it('replaces any existing popups with the same key', () => { - store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } })) - store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'def' } } })) + store.dispatch(addPopup({ key: 'abc', content: { type: PopupType.Transaction, hash: 'abc' } })) + store.dispatch(addPopup({ key: 'abc', content: { type: PopupType.Transaction, hash: 'abc' } })) const list = store.getState().popupList expect(list).toEqual([ { key: 'abc', show: true, - content: { txn: { hash: 'def' } }, + content: { type: PopupType.Transaction, hash: 'abc' }, removeAfterMs: 10000, }, ]) @@ -54,7 +55,7 @@ describe('application reducer', () => { describe('removePopup', () => { beforeEach(() => { - store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } })) + store.dispatch(addPopup({ key: 'abc', content: { type: PopupType.Transaction, hash: 'abc' } })) }) it('hides the popup', () => { @@ -64,7 +65,7 @@ describe('application reducer', () => { { key: 'abc', show: false, - content: { txn: { hash: 'abc' } }, + content: { type: PopupType.Transaction, hash: 'abc' }, removeAfterMs: 10000, }, ]) diff --git a/apps/web/src/state/application/reducer.ts b/apps/web/src/state/application/reducer.ts index ac901e294b4..c7afb5ca341 100644 --- a/apps/web/src/state/application/reducer.ts +++ b/apps/web/src/state/application/reducer.ts @@ -60,7 +60,7 @@ type AddLiquidityModalParams = { type RemoveLiquidityModalParams = { name: typeof ModalName.RemoveLiquidity - initialState?: Position + initialState: Position } export type OpenModalParams = @@ -108,7 +108,12 @@ const applicationSlice = createSlice({ state.openModal = null } }, - addPopup(state, { payload: { content, key, removeAfterMs = DEFAULT_TXN_DISMISS_MS } }) { + addPopup( + state, + { + payload: { content, key, removeAfterMs = DEFAULT_TXN_DISMISS_MS }, + }: { payload: { content: PopupContent; key?: string; removeAfterMs?: number } }, + ) { key = key || nanoid() state.popupList = [ ...state.popupList.filter((popup) => popup.key !== key), diff --git a/apps/web/src/state/burn/hooks.tsx b/apps/web/src/state/burn/hooks.tsx index b5f56026488..a72e01cb648 100644 --- a/apps/web/src/state/burn/hooks.tsx +++ b/apps/web/src/state/burn/hooks.tsx @@ -1,5 +1,6 @@ import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { useAccount } from 'hooks/useAccount' import { useTotalSupply } from 'hooks/useTotalSupply' import { useV2Pair } from 'hooks/useV2Pairs' @@ -10,8 +11,6 @@ import { ReactNode, useCallback } from 'react' import { Field, typeInput } from 'state/burn/actions' import { useAppDispatch, useAppSelector } from 'state/hooks' import { InterfaceState } from 'state/webReducer' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' export function useBurnState(): InterfaceState['burn'] { @@ -122,19 +121,9 @@ export function useDerivedBurnInfo( : undefined, } - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - let error: ReactNode | undefined if (!account.isConnected) { - error = isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - ) + error = } if (!parsedAmounts[Field.LIQUIDITY] || !parsedAmounts[Field.CURRENCY_A] || !parsedAmounts[Field.CURRENCY_B]) { diff --git a/apps/web/src/state/burn/v3/hooks.tsx b/apps/web/src/state/burn/v3/hooks.tsx index f4d26353046..981381d5039 100644 --- a/apps/web/src/state/burn/v3/hooks.tsx +++ b/apps/web/src/state/burn/v3/hooks.tsx @@ -1,5 +1,6 @@ import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { Position } from '@uniswap/v3-sdk' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { useToken } from 'hooks/Tokens' import { useAccount } from 'hooks/useAccount' import { usePool } from 'hooks/usePools' @@ -9,8 +10,6 @@ import { selectPercent } from 'state/burn/v3/actions' import { useAppDispatch, useAppSelector } from 'state/hooks' import { InterfaceState } from 'state/webReducer' import { PositionDetails } from 'types/position' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' import { unwrappedToken } from 'utils/unwrappedToken' @@ -75,19 +74,9 @@ export function useDerivedV3BurnInfo( const outOfRange = pool && position ? pool.tickCurrent < position.tickLower || pool.tickCurrent > position.tickUpper : false - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - let error: ReactNode | undefined if (!account.isConnected) { - error = isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - ) + error = } if (percent === 0) { error = error ?? diff --git a/apps/web/src/state/claim/hooks.ts b/apps/web/src/state/claim/hooks.ts index 38716dd5fb2..04ac7f48049 100644 --- a/apps/web/src/state/claim/hooks.ts +++ b/apps/web/src/state/claim/hooks.ts @@ -179,7 +179,7 @@ export function useClaimCallback(address: string | null | undefined): { const claimCallback = async function () { if (!claimData || !address || !provider || !account.chainId || !distributorContract) { - return + return undefined } const args = [claimData.index, address, claimData.amount, claimData.proof] diff --git a/apps/web/src/state/fiatOnRampTransactions/types.ts b/apps/web/src/state/fiatOnRampTransactions/types.ts index fa36557bf9b..5522d57ab06 100644 --- a/apps/web/src/state/fiatOnRampTransactions/types.ts +++ b/apps/web/src/state/fiatOnRampTransactions/types.ts @@ -7,6 +7,7 @@ export enum FiatOnRampTransactionStatus { FAILED = 'FAILED', } +// eslint-disable-next-line consistent-return export function backendStatusToFiatOnRampStatus(status: TransactionStatus) { switch (status) { case TransactionStatus.Confirmed: diff --git a/apps/web/src/state/index.ts b/apps/web/src/state/index.ts index b942e103050..4dfd196a592 100644 --- a/apps/web/src/state/index.ts +++ b/apps/web/src/state/index.ts @@ -8,6 +8,7 @@ import { sentryEnhancer } from 'state/logging' import { INDEXED_DB_REDUX_TABLE_NAME, PERSIST_VERSION, customCreateMigrate, migrations } from 'state/migrations' import { quickRouteApi } from 'state/routing/quickRouteSlice' import { routingApi } from 'state/routing/slice' +import { rootWebSaga } from 'state/sagas/root' import { InterfaceState, interfacePersistedStateList, interfaceReducer } from 'state/webReducer' import { fiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api' import { isDevEnv, isTestEnv } from 'utilities/src/environment/env' @@ -32,8 +33,10 @@ const persistConfig: PersistConfig = { const persistedReducer = persistReducer(persistConfig, interfaceReducer) +const sagaMiddleware = createSagaMiddleware() + export function createDefaultStore() { - return configureStore({ + const store = configureStore({ reducer: persistedReducer, enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer), middleware: (getDefaultMiddleware) => @@ -64,8 +67,11 @@ export function createDefaultStore() { .concat(routingApi.middleware) .concat(quickRouteApi.middleware) .concat(fiatOnRampAggregatorApi.middleware) - .concat(createSagaMiddleware()), + .concat(sagaMiddleware), }) + sagaMiddleware.run(rootWebSaga) + + return store } const store = createDefaultStore() diff --git a/apps/web/src/state/limit/expiryToDeadlineSeconds.ts b/apps/web/src/state/limit/expiryToDeadlineSeconds.ts index 7ef50d99ab4..d970cb995ce 100644 --- a/apps/web/src/state/limit/expiryToDeadlineSeconds.ts +++ b/apps/web/src/state/limit/expiryToDeadlineSeconds.ts @@ -3,6 +3,7 @@ import { LimitsExpiry } from 'uniswap/src/types/limits' const DAY_SECS = ms('1d') / 1000 +// eslint-disable-next-line consistent-return export function expiryToDeadlineSeconds(expiry: LimitsExpiry): number { switch (expiry) { case LimitsExpiry.Day: diff --git a/apps/web/src/state/limit/hooks.ts b/apps/web/src/state/limit/hooks.ts index e2b61034968..492f31d31d3 100644 --- a/apps/web/src/state/limit/hooks.ts +++ b/apps/web/src/state/limit/hooks.ts @@ -1,5 +1,4 @@ import { Currency, CurrencyAmount, Price, TradeType } from '@uniswap/sdk-core' -import { Field } from 'components/swap/constants' import { isStablecoin } from 'constants/chains' import { useAccount } from 'hooks/useAccount' import JSBI from 'jsbi' @@ -16,10 +15,11 @@ import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { nativeOnChain } from 'uniswap/src/constants/tokens' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { CurrencyField } from 'uniswap/src/types/currency' export type LimitInfo = { - currencyBalances: { [field in Field]?: CurrencyAmount } - parsedAmounts: { [field in Field]?: CurrencyAmount } + currencyBalances: { [field in CurrencyField]?: CurrencyAmount } + parsedAmounts: { [field in CurrencyField]?: CurrencyAmount } parsedLimitPrice?: Price limitOrderTrade?: LimitOrderTrade marketPrice?: Price @@ -55,8 +55,8 @@ export function useDerivedLimitInfo(state: LimitState, setState: Dispatch ({ - [Field.INPUT]: relevantTokenBalances[0], - [Field.OUTPUT]: relevantTokenBalances[1], + [CurrencyField.INPUT]: relevantTokenBalances[0], + [CurrencyField.OUTPUT]: relevantTokenBalances[1], }), [relevantTokenBalances], ) @@ -100,8 +100,8 @@ export function useDerivedLimitInfo(state: LimitState, setState: Dispatch } + parsedAmounts: { [field in CurrencyField]?: CurrencyAmount } outputAmount?: CurrencyAmount swapFee?: SwapFeeInfo }) { @@ -185,10 +185,10 @@ function useLimitOrderTrade({ }, [account.address, inputCurrency, trade]) const limitOrderTrade = useMemo(() => { - if (!inputCurrency || !parsedAmounts?.[Field.INPUT] || !account.address || !outputAmount || !wrapInfo) { + if (!inputCurrency || !parsedAmounts?.[CurrencyField.INPUT] || !account.address || !outputAmount || !wrapInfo) { return undefined } - const amountIn = CurrencyAmount.fromRawAmount(inputCurrency.wrapped, parsedAmounts?.[Field.INPUT].quotient) + const amountIn = CurrencyAmount.fromRawAmount(inputCurrency.wrapped, parsedAmounts?.[CurrencyField.INPUT].quotient) return new LimitOrderTrade({ amountIn, amountOut: outputAmount, diff --git a/apps/web/src/state/migrations/14.ts b/apps/web/src/state/migrations/14.ts index c5523feaf17..8e12ba6dd81 100644 --- a/apps/web/src/state/migrations/14.ts +++ b/apps/web/src/state/migrations/14.ts @@ -12,7 +12,7 @@ export const hideSpamBalancesAtomName = 'hideSpamBalances' */ export const migration14 = (state: PersistAppStateV14 | undefined) => { if (!state) { - return + return undefined } const newState: any = { ...state } diff --git a/apps/web/src/state/migrations/15.ts b/apps/web/src/state/migrations/15.ts index c0ae928266d..ebd9b8dab96 100644 --- a/apps/web/src/state/migrations/15.ts +++ b/apps/web/src/state/migrations/15.ts @@ -47,7 +47,7 @@ function webResultToUniswapResult(webItem: TokenSearchResultWeb): SearchResult | */ export const migration15 = (state: PersistAppStateV15 | undefined) => { if (!state) { - return + return undefined } const newState: any = { ...state } diff --git a/apps/web/src/state/migrations/16.ts b/apps/web/src/state/migrations/16.ts index c12920a2e6f..f0ca60f7160 100644 --- a/apps/web/src/state/migrations/16.ts +++ b/apps/web/src/state/migrations/16.ts @@ -15,7 +15,7 @@ export type PersistAppStateV16 = { */ export const migration16 = (state: PersistAppStateV16 | undefined) => { if (!state) { - return + return undefined } const newState: any = { ...state } diff --git a/apps/web/src/state/migrations/17.ts b/apps/web/src/state/migrations/17.ts index 640804ad58d..8eb4760636c 100644 --- a/apps/web/src/state/migrations/17.ts +++ b/apps/web/src/state/migrations/17.ts @@ -15,7 +15,7 @@ export type PersistAppStateV17 = { */ export const migration17 = (state: PersistAppStateV17 | undefined) => { if (!state) { - return + return undefined } const newState: any = { ...state } diff --git a/apps/web/src/state/migrations/18.ts b/apps/web/src/state/migrations/18.ts index 37bb861d2ef..e9e3cef0c5a 100644 --- a/apps/web/src/state/migrations/18.ts +++ b/apps/web/src/state/migrations/18.ts @@ -13,7 +13,7 @@ export type PersistAppStateV18 = { */ export const migration18 = (state: PersistAppStateV18 | undefined) => { if (!state?.userSettings) { - return + return undefined } const newState: any = { ...state } diff --git a/apps/web/src/state/migrations/19.ts b/apps/web/src/state/migrations/19.ts index 9d5a7f3ce98..3afe1a064a9 100644 --- a/apps/web/src/state/migrations/19.ts +++ b/apps/web/src/state/migrations/19.ts @@ -16,7 +16,7 @@ export type PersistAppStateV19 = { */ export const migration19 = (state: PersistAppStateV19 | undefined) => { if (!state) { - return + return undefined } // Copy state diff --git a/apps/web/src/state/migrations/20.ts b/apps/web/src/state/migrations/20.ts index 4f82071b2b7..dcf248c40ea 100644 --- a/apps/web/src/state/migrations/20.ts +++ b/apps/web/src/state/migrations/20.ts @@ -14,7 +14,7 @@ export const activeLocalCurrencyAtomName = 'activeLocalCurrency' */ export const migration20 = (state: PersistAppStateV20 | undefined) => { if (!state) { - return + return undefined } // Translate existing atom value const atomLocalCurrencyAtomValue = localStorage.getItem(activeLocalCurrencyAtomName) as FiatCurrency diff --git a/apps/web/src/state/mint/hooks.tsx b/apps/web/src/state/mint/hooks.tsx index 9bb1d08162d..941b516031a 100644 --- a/apps/web/src/state/mint/hooks.tsx +++ b/apps/web/src/state/mint/hooks.tsx @@ -1,5 +1,6 @@ import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { useAccount } from 'hooks/useAccount' import { useTotalSupply } from 'hooks/useTotalSupply' import { PairState, useV2Pair } from 'hooks/useV2Pairs' @@ -10,8 +11,6 @@ import { useCurrencyBalances } from 'state/connection/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks' import { Field, typeInput } from 'state/mint/actions' import { InterfaceState } from 'state/webReducer' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' import { logger } from 'utilities/src/logger/logger' @@ -183,19 +182,9 @@ export function useDerivedMintInfo( } }, [liquidityMinted, totalSupply]) - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - let error: ReactNode | undefined if (!account.isConnected) { - error = isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - ) + error = } if (pairState === PairState.INVALID) { diff --git a/apps/web/src/state/mint/v3/hooks.tsx b/apps/web/src/state/mint/v3/hooks.tsx index 2f1c4da3a0e..24e91a054c3 100644 --- a/apps/web/src/state/mint/v3/hooks.tsx +++ b/apps/web/src/state/mint/v3/hooks.tsx @@ -10,6 +10,7 @@ import { priceToClosestTick, tickToPrice, } from '@uniswap/v3-sdk' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { BIG_INT_ZERO } from 'constants/misc' import { useAccount } from 'hooks/useAccount' import { PoolState, usePool } from 'hooks/usePools' @@ -31,8 +32,6 @@ import { } from 'state/mint/v3/actions' import { tryParseTick } from 'state/mint/v3/utils' import { InterfaceState } from 'state/webReducer' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' import { getTickToPrice } from 'utils/getTickToPrice' @@ -450,19 +449,9 @@ export function useV3DerivedMintInfo( tickUpper, ]) - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - let errorMessage: ReactNode | undefined if (!account.isConnected) { - errorMessage = isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - ) + errorMessage = } if (poolState === PoolState.INVALID) { diff --git a/apps/web/src/state/routing/slice.ts b/apps/web/src/state/routing/slice.ts index f1e88045a54..e88e03e3746 100644 --- a/apps/web/src/state/routing/slice.ts +++ b/apps/web/src/state/routing/slice.ts @@ -1,6 +1,5 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { Protocol } from '@uniswap/router-sdk' -import { isUniswapXSupportedChain } from 'constants/chains' import ms from 'ms' import { ClassicAPIConfig, @@ -15,6 +14,7 @@ import { URAQuoteResponse, URAQuoteType, UniswapXConfig, + UniswapXPriorityOrdersConfig, UniswapXv2Config, } from 'state/routing/types' import { isExactInput, transformQuoteToTrade } from 'state/routing/utils' @@ -44,7 +44,6 @@ const DEFAULT_QUERY_PARAMS = { function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { const { account, - tokenInChainId, uniswapXForceSyntheticQuotes, routerPreference, protocolPreferences, @@ -53,6 +52,8 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { forceOpenOrders, deadlineBufferSecs, isXv2Arbitrum, + isPriorityOrder, + isUniswapXSupportedChain, } = args const uniswapX: UniswapXConfig = { @@ -61,6 +62,11 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { routingType: URAQuoteType.DUTCH_V1, } + const uniswapXPriorityOrders: UniswapXPriorityOrdersConfig = { + routingType: URAQuoteType.PRIORITY, + swapper: account, + } + const uniswapXv2: UniswapXv2Config = { useSyntheticQuotes: uniswapXForceSyntheticQuotes || isXv2Arbitrum, swapper: account, @@ -86,12 +92,12 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { // If the user has opted out of UniswapX during the opt-out transition period, we should respect that preference and only request classic quotes. routerPreference === RouterPreference.API || routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE || - (!isUniswapXSupportedChain(tokenInChainId) && !isXv2Arbitrum) + !isUniswapXSupportedChain ) { return [classic] } - return [isXv2 || isXv2Arbitrum ? uniswapXv2 : uniswapX, classic] + return [isPriorityOrder ? uniswapXPriorityOrders : isXv2 || isXv2Arbitrum ? uniswapXv2 : uniswapX, classic] } export const routingApi = createApi({ diff --git a/apps/web/src/state/routing/types.ts b/apps/web/src/state/routing/types.ts index ee8c9e14e4b..df471da34b2 100644 --- a/apps/web/src/state/routing/types.ts +++ b/apps/web/src/state/routing/types.ts @@ -6,7 +6,10 @@ import { DutchOrderInfo, DutchOrderInfoJSON, DutchOrderTrade as IDutchOrderTrade, + PriorityOrderTrade as IPriorityOrderTrade, V2DutchOrderTrade as IV2DutchOrderTrade, + UnsignedPriorityOrderInfo, + UnsignedPriorityOrderInfoJSON, UnsignedV2DutchOrderInfo, UnsignedV2DutchOrderInfoJSON, } from '@uniswap/uniswapx-sdk' @@ -71,6 +74,8 @@ export interface GetQuoteArgs { forceOpenOrders: boolean deadlineBufferSecs: number arbitrumXV2SlippageTolerance: string + isPriorityOrder: boolean + isUniswapXSupportedChain: boolean } export type GetQuickQuoteArgs = { @@ -187,6 +192,24 @@ export type URADutchOrderV2QuoteData = { portionRecipient?: string } +// from `PriorityQuoteDataJSON` in https://github.com/Uniswap/backend/blob/main/packages/services/unified-routing-api/lib/entities/quote/PriorityQuote.ts +export type URAPriorityOrderQuoteData = { + orderInfo: UnsignedPriorityOrderInfoJSON + startTimeBufferSecs: number // ignore for priority order + deadlineBufferSecs: number // ignore for priority order + amountInMpsPerPriorityFeeWei: number + amountOutMpsPerPriorityFeeWei: number + permitData: PermitTransferFromData + quoteId: string + requestId: string + encodedOrder: string + orderHash: string + slippageTolerance: string + portionBips?: number + portionAmount?: string + portionRecipient?: string +} + type URADutchOrderQuoteResponse = { routing: URAQuoteType.DUTCH_V1 quote: URADutchOrderQuoteData @@ -202,7 +225,16 @@ type URAClassicQuoteResponse = { quote: ClassicQuoteData allQuotes: Array } -export type URAQuoteResponse = URAClassicQuoteResponse | URADutchOrderQuoteResponse | URADutchOrderV2QuoteResponse +type URAPriorityOrderQuoteResponse = { + routing: URAQuoteType.PRIORITY + quote: URAPriorityOrderQuoteData + allQuotes: Array +} +export type URAQuoteResponse = + | URAClassicQuoteResponse + | URADutchOrderQuoteResponse + | URADutchOrderV2QuoteResponse + | URAPriorityOrderQuoteResponse export type QuickRouteResponse = { tokenIn: { @@ -333,6 +365,7 @@ export enum OffchainOrderType { DUTCH_V2_AUCTION = 'Dutch_V2', LIMIT_ORDER = 'Limit', DUTCH_V1_AND_V2 = 'Dutch_V1_V2', // Only used for GET /orders queries. Returns both Dutch V1 and V2 orders. + PRIORITY_ORDER = 'Priority', } export class DutchOrderTrade extends IDutchOrderTrade { @@ -497,6 +530,81 @@ export class V2DutchOrderTrade extends IV2DutchOrderTrade { + public readonly fillType = TradeFillType.UniswapX + public readonly offchainOrderType = OffchainOrderType.PRIORITY_ORDER + + quoteId?: string + requestId?: string + wrapInfo: WrapInfo + approveInfo: ApproveInfo + // The gas estimate of the reference classic trade, if there is one. + classicGasUseEstimateUSD?: number + startTimeBufferSecs: number + deadlineBufferSecs: number + slippageTolerance: Percent + + inputTax = ZERO_PERCENT + outputTax = ZERO_PERCENT + swapFee: SwapFeeInfo | undefined + + constructor({ + currencyIn, + currenciesOut, + orderInfo, + tradeType, + quoteId, + requestId, + wrapInfo, + approveInfo, + classicGasUseEstimateUSD, + startTimeBufferSecs, + deadlineBufferSecs, + slippageTolerance, + swapFee, + }: { + currencyIn: Currency + currenciesOut: Currency[] + orderInfo: UnsignedPriorityOrderInfo + tradeType: TradeType + quoteId?: string + requestId?: string + approveInfo: ApproveInfo + wrapInfo: WrapInfo + classicGasUseEstimateUSD?: number + startTimeBufferSecs: number + deadlineBufferSecs: number + slippageTolerance: Percent + swapFee?: SwapFeeInfo + }) { + super({ currencyIn, currenciesOut, orderInfo, tradeType }) + this.quoteId = quoteId + this.requestId = requestId + this.approveInfo = approveInfo + this.wrapInfo = wrapInfo + this.classicGasUseEstimateUSD = classicGasUseEstimateUSD + this.deadlineBufferSecs = deadlineBufferSecs + this.slippageTolerance = slippageTolerance + this.startTimeBufferSecs = startTimeBufferSecs + this.swapFee = swapFee + } + + public get totalGasUseEstimateUSD(): number { + if (this.wrapInfo.needsWrap && this.approveInfo.needsApprove) { + return this.wrapInfo.wrapGasEstimateUSD + this.approveInfo.approveGasEstimateUSD + } + + if (this.wrapInfo.needsWrap) { + return this.wrapInfo.wrapGasEstimateUSD + } + if (this.approveInfo.needsApprove) { + return this.approveInfo.approveGasEstimateUSD + } + + return 0 + } +} + export class PreviewTrade { public readonly fillType = TradeFillType.None public readonly quoteMethod = QuoteMethod.QUICK_ROUTE @@ -745,7 +853,7 @@ export class LimitOrderTrade { } } -export type SubmittableTrade = ClassicTrade | DutchOrderTrade | V2DutchOrderTrade | LimitOrderTrade +export type SubmittableTrade = ClassicTrade | DutchOrderTrade | V2DutchOrderTrade | LimitOrderTrade | PriorityOrderTrade export type InterfaceTrade = SubmittableTrade | PreviewTrade export enum QuoteState { @@ -806,9 +914,10 @@ export enum URAQuoteType { CLASSIC = 'CLASSIC', DUTCH_V1 = 'DUTCH_LIMIT', // "dutch limit" refers to dutch. Fully separate from "limit orders" DUTCH_V2 = 'DUTCH_V2', + PRIORITY = 'PRIORITY', } -/* Config types should match URA config schemas `classicConfig`, `dutchLimitConfig`, and `dutchV2Config` at https://github.com/Uniswap/unified-routing-api/blob/main/lib/util/validator.ts */ +/* Config types should match URA config schemas https://github.com/Uniswap/backend/blob/main/packages/services/unified-routing-api/lib/util/validator.ts */ export type ClassicAPIConfig = { routingType: URAQuoteType.CLASSIC @@ -853,4 +962,13 @@ export type UniswapXv2Config = { slippageTolerance?: string } -export type RoutingConfig = (UniswapXConfig | UniswapXv2Config | ClassicAPIConfig)[] +export type UniswapXPriorityOrdersConfig = { + routingType: URAQuoteType.PRIORITY + swapper?: string + mpsPerPriorityFeeWei?: number + baselinePriorityFeeWei?: number + startTimeBufferSecs?: number + deadlineBufferSecs?: number +} + +export type RoutingConfig = (UniswapXConfig | UniswapXv2Config | ClassicAPIConfig | UniswapXPriorityOrdersConfig)[] diff --git a/apps/web/src/state/routing/useRoutingAPITrade.test.ts b/apps/web/src/state/routing/useRoutingAPITrade.test.ts index d313b0913ba..9bdd8566c05 100644 --- a/apps/web/src/state/routing/useRoutingAPITrade.test.ts +++ b/apps/web/src/state/routing/useRoutingAPITrade.test.ts @@ -81,6 +81,8 @@ const MOCK_ARGS: GetQuoteArgs = { deadlineBufferSecs: 0, arbitrumXV2SlippageTolerance: undefined as any, protocolPreferences: undefined, + isPriorityOrder: false, + isUniswapXSupportedChain: true, } describe('#useRoutingAPITrade ExactIn', () => { diff --git a/apps/web/src/state/routing/utils.test.ts b/apps/web/src/state/routing/utils.test.ts index 7ce882d9a02..0c299c074f7 100644 --- a/apps/web/src/state/routing/utils.test.ts +++ b/apps/web/src/state/routing/utils.test.ts @@ -43,6 +43,8 @@ function constructArgs(currencyIn: Currency, currencyOut: Currency, isXv2?: bool forceOpenOrders: false, deadlineBufferSecs: 30, arbitrumXV2SlippageTolerance: '0.5', + isPriorityOrder: false, + isUniswapXSupportedChain: true, } } diff --git a/apps/web/src/state/routing/utils.ts b/apps/web/src/state/routing/utils.ts index a506f9e8e8d..1c6626f015a 100644 --- a/apps/web/src/state/routing/utils.ts +++ b/apps/web/src/state/routing/utils.ts @@ -5,6 +5,9 @@ import { DutchOrderInfo, DutchOrderInfoJSON, DutchOutputJSON, + PriorityOutputJSON, + UnsignedPriorityOrderInfo, + UnsignedPriorityOrderInfoJSON, UnsignedV2DutchOrderInfo, UnsignedV2DutchOrderInfoJSON, } from '@uniswap/uniswapx-sdk' @@ -23,6 +26,7 @@ import { OffchainOrderType, PoolType, PreviewTrade, + PriorityOrderTrade, QuickRouteResponse, QuoteMethod, QuoteState, @@ -35,6 +39,7 @@ import { TradeResult, URADutchOrderQuoteData, URADutchOrderV2QuoteData, + URAPriorityOrderQuoteData, URAQuoteResponse, URAQuoteType, V2DutchOrderTrade, @@ -90,7 +95,7 @@ export function computeRoutes(args: GetQuoteArgs, routes: ClassicQuoteData['rout }) } catch (e) { logger.warn('routing/utils', 'computeRoutes', 'Failed to compute routes', { error: e }) - return + return undefined } } @@ -142,6 +147,28 @@ function toUnsignedV2DutchOrderInfo(orderInfoJSON: UnsignedV2DutchOrderInfoJSON) } } +function toUnsignedPriorityOrderInfo(orderInfoJSON: UnsignedPriorityOrderInfoJSON): UnsignedPriorityOrderInfo { + const { nonce, auctionStartBlock, baselinePriorityFeeWei, input, outputs } = orderInfoJSON + + return { + ...orderInfoJSON, + nonce: BigNumber.from(nonce), + auctionStartBlock: BigNumber.from(auctionStartBlock), + baselinePriorityFeeWei: BigNumber.from(baselinePriorityFeeWei), + + input: { + ...input, + amount: BigNumber.from(input.amount), + mpsPerPriorityFeeWei: BigNumber.from(input.mpsPerPriorityFeeWei), + }, + outputs: outputs.map((output: PriorityOutputJSON) => ({ + ...output, + amount: BigNumber.from(output.amount), + mpsPerPriorityFeeWei: BigNumber.from(output.mpsPerPriorityFeeWei), + })), + } +} + // Prepares the currencies used for the actual Swap (either UniswapX or Universal Router) // May not match `currencyIn` that the user selected because for ETH inputs in UniswapX, the actual // swap will use WETH. @@ -196,7 +223,7 @@ function getTradeCurrencies( } function getSwapFee( - data: ClassicQuoteData | URADutchOrderQuoteData | URADutchOrderV2QuoteData, + data: ClassicQuoteData | URADutchOrderQuoteData | URADutchOrderV2QuoteData | URAPriorityOrderQuoteData, ): SwapFeeInfo | undefined { const { portionAmount, portionBips, portionRecipient } = data @@ -264,7 +291,8 @@ export async function transformQuoteToTrade( const { tradeType, needsWrapIfUniswapX, isXv2, routerPreference, account, amount } = args const showUniswapXTrade = - (isXv2 ? data.routing === URAQuoteType.DUTCH_V2 : data.routing === URAQuoteType.DUTCH_V1) && + ((isXv2 ? data.routing === URAQuoteType.DUTCH_V2 : data.routing === URAQuoteType.DUTCH_V1) || + data.routing === URAQuoteType.PRIORITY) && routerPreference === RouterPreference.X const [currencyIn, currencyOut] = getTradeCurrencies(args, showUniswapXTrade) @@ -313,7 +341,11 @@ export async function transformQuoteToTrade( }) // If the top-level URA quote type is DUTCH_V1 or DUTCH_V2, then UniswapX is better for the user - const isUniswapXBetter = data.routing === URAQuoteType.DUTCH_V1 || data.routing === URAQuoteType.DUTCH_V2 + // Or if quote type is PRIORITY, we only use UniswapX + const isUniswapXBetter = + data.routing === URAQuoteType.DUTCH_V1 || + data.routing === URAQuoteType.DUTCH_V2 || + data.routing === URAQuoteType.PRIORITY if (isUniswapXBetter) { const swapFee = getSwapFee(data.quote) const wrapInfo = await getWrapInfo(needsWrapIfUniswapX, account, currencyIn.chainId, amount, usdCostPerGas) @@ -363,6 +395,26 @@ export async function transformQuoteToTrade( state: QuoteState.SUCCESS, trade: uniswapXTrade, } + } else if (data.routing === URAQuoteType.PRIORITY) { + const orderInfo = toUnsignedPriorityOrderInfo(data.quote.orderInfo) + const priorityOrderTrade = new PriorityOrderTrade({ + currencyIn, + currenciesOut: [currencyOut], + orderInfo, + tradeType, + approveInfo, + wrapInfo, + startTimeBufferSecs: data.quote.startTimeBufferSecs, + deadlineBufferSecs: data.quote.deadlineBufferSecs, + slippageTolerance: toSlippagePercent(data.quote.slippageTolerance), + classicGasUseEstimateUSD: classicTrade.totalGasUseEstimateUSD, + swapFee, + }) + + return { + state: QuoteState.SUCCESS, + trade: priorityOrderTrade, + } } } @@ -435,16 +487,19 @@ export function isUniswapXTradeType( export function isUniswapXTrade( trade?: InterfaceTrade, -): trade is DutchOrderTrade | V2DutchOrderTrade | LimitOrderTrade { +): trade is DutchOrderTrade | V2DutchOrderTrade | LimitOrderTrade | PriorityOrderTrade { return isUniswapXTradeType(trade?.fillType) } /* Returns true if trade is a SWAP on UniswapX, not a limit order */ -export function isUniswapXSwapTrade(trade?: InterfaceTrade): trade is DutchOrderTrade | V2DutchOrderTrade { +export function isUniswapXSwapTrade( + trade?: InterfaceTrade, +): trade is DutchOrderTrade | V2DutchOrderTrade | PriorityOrderTrade { return ( isUniswapXTrade(trade) && (trade?.offchainOrderType === OffchainOrderType.DUTCH_AUCTION || - trade?.offchainOrderType === OffchainOrderType.DUTCH_V2_AUCTION) + trade?.offchainOrderType === OffchainOrderType.DUTCH_V2_AUCTION || + trade?.offchainOrderType === OffchainOrderType.PRIORITY_ORDER) ) } diff --git a/apps/web/src/state/sagas/root.ts b/apps/web/src/state/sagas/root.ts new file mode 100644 index 00000000000..c70f5bfbaf7 --- /dev/null +++ b/apps/web/src/state/sagas/root.ts @@ -0,0 +1,20 @@ +import { PersistState } from 'redux-persist' +import { swapSaga } from 'state/sagas/transactions/swapSaga' +import { wrapSaga } from 'state/sagas/transactions/wrapSaga' +import { delay, select, spawn } from 'typed-redux-saga' + +const sagas = [swapSaga.wrappedSaga, wrapSaga.wrappedSaga] + +export function* rootWebSaga() { + // wait until redux-persist has finished rehydration + while (true) { + if (yield* select((state: { _persist?: PersistState }): boolean | undefined => state._persist?.rehydrated)) { + break + } + yield* delay(/* REHYDRATION_STATUS_POLLING_INTERVAL */ 50) + } + + for (const wrappedSaga of sagas) { + yield* spawn(wrappedSaga) + } +} diff --git a/apps/web/src/state/sagas/transactions/swapSaga.ts b/apps/web/src/state/sagas/transactions/swapSaga.ts new file mode 100644 index 00000000000..955fda5388b --- /dev/null +++ b/apps/web/src/state/sagas/transactions/swapSaga.ts @@ -0,0 +1,319 @@ +/* eslint-disable rulesdir/no-undefined-or */ +import { TransactionResponse } from '@ethersproject/providers' +import { SwapEventName } from '@uniswap/analytics-events' +import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics' +import { useAccount } from 'hooks/useAccount' +import useSelectChain from 'hooks/useSelectChain' +import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' +import { useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { PopupType, addPopup } from 'state/application/reducer' +import { handleUniswapXSignatureStep } from 'state/sagas/transactions/uniswapx' +import { + HandleOnChainStepParams, + getSwapTransactionInfo, + handleApprovalTransactionStep, + handleOnChainStep, + handleSignatureStep, +} from 'state/sagas/transactions/utils' +import { handleWrapStep } from 'state/sagas/transactions/wrapSaga' +import invariant from 'tiny-invariant' +import { call, put } from 'typed-redux-saga' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { SignerMnemonicAccountMeta } from 'uniswap/src/features/accounts/types' +import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { selectSwapStartTimestamp } from 'uniswap/src/features/timing/selectors' +import { updateSwapStartTimestamp } from 'uniswap/src/features/timing/slice' +import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' +import { + SetCurrentStepFn, + SwapCallback, + SwapCallbackParams, +} from 'uniswap/src/features/transactions/swap/types/swapCallback' +import { + ValidatedClassicSwapTxAndGasInfo, + ValidatedSwapTxContext, + ValidatedUniswapXSwapTxAndGasInfo, +} from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' +import { ClassicTrade } from 'uniswap/src/features/transactions/swap/types/trade' +import { + SwapTransactionStep, + SwapTransactionStepAsync, + TransactionStep, + TransactionStepType, + generateTransactionSteps, +} from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing' +import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi' +import { createSaga } from 'uniswap/src/utils/saga' +import { percentFromFloat } from 'utilities/src/format/percent' +import { logger } from 'utilities/src/logger/logger' + +// TODO(WEB-4921): Move errors to uniswap package and handle them in UI +class UnexpectedSwapStateError extends Error { + constructor(message: string) { + super(message) + this.name = 'UnexpectedSwapStateError' + } +} + +interface HandleSwapStepParams extends Omit { + step: SwapTransactionStep | SwapTransactionStepAsync + signature: string | undefined + trade: ClassicTrade + analytics: ReturnType +} +function* handleSwapTransactionStep(params: HandleSwapStepParams) { + const { trade, step, signature, analytics } = params + const info = getSwapTransactionInfo(trade) + const txRequest = yield* call(getSwapTxRequest, step, signature) + + const onModification = (response: TransactionResponse) => { + sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { + txHash: response.hash, + expected: txRequest.data?.toString() ?? '', + actual: response.data, + }) + } + + // Now that we have the txRequest, we can create a definitive SwapTransactionStep, incase we started with an async step. + const onChainStep = { ...step, txRequest } + const hash = yield* call(handleOnChainStep, { ...params, info, step: onChainStep, onModification }) + + sendAnalyticsEvent( + SwapEventName.SWAP_SIGNED, + formatSwapSignedAnalyticsEventProperties({ + trade, + allowedSlippage: percentFromFloat(trade.slippageTolerance), + fiatValues: { + amountIn: analytics.token_in_amount_usd, + amountOut: analytics.token_out_amount_usd, + feeUsd: analytics.fee_usd, + }, + txHash: hash, + portfolioBalanceUsd: analytics.total_balances_usd, + }), + ) + + yield* put(addPopup({ content: { type: PopupType.Transaction, hash }, key: hash })) + + return +} + +function* getSwapTxRequest(step: SwapTransactionStep | SwapTransactionStepAsync, signature: string | undefined) { + if (step.type === TransactionStepType.SwapTransaction) { + return step.txRequest + } + + if (!signature) { + throw new UnexpectedSwapStateError('Signature required for async swap transaction step') + } + + try { + const txRequest = yield* call(step.getTxRequest, signature) + invariant(txRequest !== undefined) + + return txRequest + } catch { + throw new UnexpectedSwapStateError('Failed to get transaction request') + } +} + +type SwapParams = { + selectChain: (chainId: number) => Promise + startChainId?: number + account: SignerMnemonicAccountMeta + analytics: ReturnType + swapTxContext: ValidatedSwapTxContext + setCurrentStep: SetCurrentStepFn + setSteps: (steps: TransactionStep[]) => void + onSuccess: () => void + onFailure: () => void +} + +// eslint-disable-next-line consistent-return +function* swap(params: SwapParams) { + const { analytics, swapTxContext, selectChain, startChainId, onFailure } = params + const steps = yield* call(generateTransactionSteps, swapTxContext) + params.setSteps(steps) + + // Switch chains if needed + const swapChainId = swapTxContext.trade.inputAmount.currency.chainId + if (swapChainId !== startChainId) { + const chainSwitched = yield* call(selectChain, swapChainId) + if (!chainSwitched) { + onFailure() + return undefined + } + } + + switch (swapTxContext.routing) { + case Routing.CLASSIC: + return yield* classicSwap({ + ...params, + swapTxContext, + steps, + analytics, + }) + case Routing.DUTCH_V2: + return yield* uniswapXSwap({ ...params, swapTxContext, steps }) + // case Routing.BRIDGE: + // return yield* bridgingSaga({ ...params, swapTxContext }) + } +} + +function* classicSwap( + params: SwapParams & { swapTxContext: ValidatedClassicSwapTxAndGasInfo; steps: TransactionStep[] }, +) { + const { + account, + setCurrentStep, + steps, + swapTxContext: { trade }, + analytics, + } = params + + let signature: string | undefined + + try { + for (const step of steps) { + switch (step.type) { + case TransactionStepType.TokenRevocationTransaction: + case TransactionStepType.TokenApprovalTransaction: { + yield* call(handleApprovalTransactionStep, { account, step, setCurrentStep }) + break + } + case TransactionStepType.Permit2Signature: { + signature = yield* call(handleSignatureStep, { account, step, setCurrentStep }) + break + } + case TransactionStepType.SwapTransaction: + case TransactionStepType.SwapTransactionAsync: { + yield* call(handleSwapTransactionStep, { account, signature, step, setCurrentStep, trade, analytics }) + break + } + default: { + throw new UnexpectedSwapStateError('Unexpected step type') + } + } + } + } catch (e) { + // TODO(WEB-4921): pass errors to onFailure and to handle in UI + logger.error(e, { tags: { file: 'swapSaga', function: 'classicSwap' } }) + } + + yield* call(params.onSuccess) +} + +function* uniswapXSwap( + params: SwapParams & { + swapTxContext: ValidatedUniswapXSwapTxAndGasInfo + steps: TransactionStep[] + analytics: ReturnType + }, +) { + const { + account, + setCurrentStep, + steps, + swapTxContext: { trade }, + analytics, + } = params + + try { + for (const step of steps) { + switch (step.type) { + case TransactionStepType.WrapTransaction: { + yield* call(handleWrapStep, { account, step, setCurrentStep }) + break + } + case TransactionStepType.TokenRevocationTransaction: + case TransactionStepType.TokenApprovalTransaction: { + yield* call(handleApprovalTransactionStep, { account, step, setCurrentStep }) + break + } + case TransactionStepType.UniswapXSignature: { + yield* call(handleUniswapXSignatureStep, { account, step, setCurrentStep, trade, analytics }) + break + } + default: { + throw new UnexpectedSwapStateError('Unexpected step type') + } + } + } + } catch (e) { + // TODO(WEB-4921): pass errors to onFailure and to handle in UI + logger.error(e, { tags: { file: 'swapSaga', function: 'uniswapXSwap' } }) + } + + yield* call(params.onSuccess) +} + +export const swapSaga = createSaga(swap, 'swapSaga') + +/** Callback to submit trades and track progress */ +export function useSwapCallback(): SwapCallback { + const appDispatch = useDispatch() + const formatter = useLocalizationContext() + const swapStartTimestamp = useSelector(selectSwapStartTimestamp) + const selectChain = useSelectChain() + const startChainId = useAccount().chainId + + const portfolioBalanceUsd = useTotalBalancesUsdForAnalytics() + + return useCallback( + (args: SwapCallbackParams) => { + const { + account, + swapTxContext, + onSuccess, + onFailure, + currencyInAmountUSD, + currencyOutAmountUSD, + isAutoSlippage, + isFiatInputMode, + setCurrentStep, + setSteps, + } = args + const { trade, gasFee } = swapTxContext + + const analytics = getBaseTradeAnalyticsProperties({ + formatter, + trade, + currencyInAmountUSD, + currencyOutAmountUSD, + portfolioBalanceUsd, + }) + const swapParams = { + swapTxContext, + account, + analytics, + onSuccess, + onFailure, + setCurrentStep, + setSteps, + selectChain, + startChainId, + } + appDispatch(swapSaga.actions.trigger(swapParams)) + + const blockNumber = getClassicQuoteFromResponse(trade?.quote)?.blockNumber?.toString() + + sendAnalyticsEvent(SwapEventName.SWAP_SUBMITTED_BUTTON_CLICKED, { + ...analytics, + estimated_network_fee_wei: gasFee.value, + gas_limit: isClassic(swapTxContext) ? swapTxContext.txRequest?.gasLimit?.toString() : undefined, + transaction_deadline_seconds: trade.deadline, + swap_quote_block_number: blockNumber, + is_auto_slippage: isAutoSlippage, + swap_flow_duration_milliseconds: swapStartTimestamp ? Date.now() - swapStartTimestamp : undefined, + is_fiat_input_mode: isFiatInputMode, + }) + + // Reset swap start timestamp now that the swap has been submitted + appDispatch(updateSwapStartTimestamp({ timestamp: undefined })) + }, + [formatter, portfolioBalanceUsd, selectChain, startChainId, appDispatch, swapStartTimestamp], + ) +} diff --git a/apps/web/src/state/sagas/transactions/uniswapx.ts b/apps/web/src/state/sagas/transactions/uniswapx.ts new file mode 100644 index 00000000000..ccd015c25b0 --- /dev/null +++ b/apps/web/src/state/sagas/transactions/uniswapx.ts @@ -0,0 +1,86 @@ +import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' +import { PopupType, addPopup } from 'state/application/reducer' +import { HandleSignatureStepParams, getSwapTransactionInfo, handleSignatureStep } from 'state/sagas/transactions/utils' +import { addSignature } from 'state/signatures/reducer' +import { SignatureType, UnfilledUniswapXOrderDetails } from 'state/signatures/types' +import { call, put } from 'typed-redux-saga' +import { UniswapXOrderStatus } from 'types/uniswapx' +import { submitOrder } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' +import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' +import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' +import { UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade' +import { UniswapXSignatureStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import { UniverseChainId } from 'uniswap/src/types/chains' +import { percentFromFloat } from 'utilities/src/format/percent' +import { logger } from 'utilities/src/logger/logger' + +interface HandleUniswapXSignatureStepParams extends HandleSignatureStepParams { + trade: UniswapXTrade + analytics: ReturnType +} +export function* handleUniswapXSignatureStep(params: HandleUniswapXSignatureStepParams) { + const { trade, analytics } = params + const quote = trade.quote.quote + const orderHash = quote.orderId + const chainId = trade.inputAmount.currency.chainId + const signatureDetails = getUniswapXSignatureInfo(params.step, trade, chainId) + + const analyticsParams: Parameters[0] = { + trade, + allowedSlippage: percentFromFloat(trade.slippageTolerance), + fiatValues: { + amountIn: analytics.token_in_amount_usd, + amountOut: analytics.token_out_amount_usd, + feeUsd: analytics.fee_usd, + }, + portfolioBalanceUsd: analytics.total_balances_usd, + } + + try { + const signature = yield* call(handleSignatureStep, params) + + sendAnalyticsEvent( + InterfaceEventNameLocal.UniswapXSignatureRequested, + formatSwapSignedAnalyticsEventProperties(analyticsParams), + ) + + yield* call(submitOrder, { signature, quote, routing: trade.routing }) + + sendAnalyticsEvent( + InterfaceEventNameLocal.UniswapXOrderSubmitted, + formatSwapSignedAnalyticsEventProperties(analyticsParams), + ) + + yield* put(addSignature(signatureDetails)) + + yield* put(addPopup({ content: { type: PopupType.Order, orderHash }, key: orderHash })) + } catch (e) { + // TODO(WEB-4921): pass errors to onFailure and to handle in UI + logger.error(e, { tags: { file: 'uniswapx', function: 'handleUniswapXSignatureStep' } }) + + sendAnalyticsEvent(InterfaceEventNameLocal.UniswapXOrderPostError, { + ...formatSwapSignedAnalyticsEventProperties(analyticsParams), + detail: e.message, + }) + } +} + +function getUniswapXSignatureInfo( + step: UniswapXSignatureStep, + trade: UniswapXTrade, + chainId: UniverseChainId, +): UnfilledUniswapXOrderDetails { + const swapInfo = getSwapTransactionInfo(trade) + + return { + type: SignatureType.SIGN_UNISWAPX_V2_ORDER, + id: step.quote.orderId, + addedTime: Date.now(), + chainId, + offerer: trade.quote.quote.orderInfo.reactor, + orderHash: trade.quote.quote.orderId, + status: UniswapXOrderStatus.OPEN, + swapInfo, + } +} diff --git a/apps/web/src/state/sagas/transactions/utils.ts b/apps/web/src/state/sagas/transactions/utils.ts new file mode 100644 index 00000000000..2e4c4460287 --- /dev/null +++ b/apps/web/src/state/sagas/transactions/utils.ts @@ -0,0 +1,169 @@ +import { JsonRpcSigner, TransactionResponse, Web3Provider } from '@ethersproject/providers' +import { TradeType } from '@uniswap/sdk-core' +import { wagmiConfig } from 'components/Web3Provider/wagmiConfig' +import { clientToProvider } from 'hooks/useEthersProvider' +import { addTransaction, finalizeTransaction } from 'state/transactions/reducer' +import { + ApproveTransactionInfo, + ExactInputSwapTransactionInfo, + ExactOutputSwapTransactionInfo, + TransactionInfo, + TransactionType, +} from 'state/transactions/types' +import { call, put, take } from 'typed-redux-saga' +import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { AccountMeta } from 'uniswap/src/features/accounts/types' +import { SetCurrentStepFn } from 'uniswap/src/features/transactions/swap/types/swapCallback' +import { ClassicTrade, UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade' +import { + OnChainTransactionStep, + SignatureTransactionStep, + TokenApprovalTransactionStep, + TokenRevocationTransactionStep, + TransactionStep, +} from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' +import { currencyId } from 'uniswap/src/utils/currencyId' +import { percentFromFloat } from 'utilities/src/format/percent' +import { getConnectorClient } from 'wagmi/actions' + +class MissingProviderError extends Error { + constructor(message: string) { + super(message) + this.name = 'MissingProviderError' + } +} +class TransactionFailureError extends Error { + step: TransactionStep + + constructor(message: string, step: TransactionStep) { + super(message) + this.name = 'TransactionFailureError' + this.step = step + } +} + +export interface HandleSignatureStepParams { + step: T + setCurrentStep: SetCurrentStepFn +} +export function* handleSignatureStep({ setCurrentStep, step }: HandleSignatureStepParams) { + // Trigger UI prompting user to accept + setCurrentStep({ step, accepted: false }) + + const signer = yield* call(getSigner) + const signature = yield* call([signer, '_signTypedData'], step.domain, step.types, step.values) + + return signature +} + +export interface HandleOnChainStepParams { + account: AccountMeta + info: TransactionInfo + step: T + setCurrentStep: SetCurrentStepFn + shouldWaitForConfirmation?: boolean + onModification?: (response: TransactionResponse) => void +} +export function* handleOnChainStep(params: HandleOnChainStepParams) { + const { account, step, setCurrentStep, info, shouldWaitForConfirmation = true, onModification } = params + const signer = yield* call(getSigner) + + // Trigger UI prompting user to accept + setCurrentStep({ step, accepted: false }) + + const response = yield* call([signer, 'sendTransaction'], step.txRequest) + const { hash, nonce, data } = response + + // Trigger waiting UI after user accepts + setCurrentStep({ step, accepted: true }) + + // Add transaction to local state to start polling for status + yield* put(addTransaction({ from: account.address, info, hash, nonce, chainId: step.txRequest.chainId })) + + if (step.txRequest.data !== data) { + onModification?.(response) + } + + if (shouldWaitForConfirmation) { + // Delay returning until transaction is confirmed + yield* call(waitForTransaction, hash, step) + } + + return hash +} + +interface HandleApprovalStepParams + extends Omit, 'info'> {} +export function* handleApprovalTransactionStep(params: HandleApprovalStepParams) { + const { step } = params + const info = getApprovalTransactionInfo(step) + return yield* call(handleOnChainStep, { ...params, info }) +} + +function getApprovalTransactionInfo( + approvalStep: TokenApprovalTransactionStep | TokenRevocationTransactionStep, +): ApproveTransactionInfo { + return { + type: TransactionType.APPROVAL, + tokenAddress: approvalStep.token.address, + spender: approvalStep.spender, + amount: approvalStep.amount, + } +} + +/** Returns when a transaction is confirmed in local state. Throws an error if the transaction fails. */ +function* waitForTransaction(hash: string, step: TransactionStep) { + while (true) { + const { payload } = yield* take>(finalizeTransaction.type) + if (payload.hash === hash) { + if (payload.status === TransactionStatus.Confirmed) { + return + } else { + throw new TransactionFailureError('Transaction not successful', step) + } + } + } +} + +async function getProvider(): Promise { + const client = await getConnectorClient(wagmiConfig) + const provider = clientToProvider(client) + + if (!provider) { + throw new MissingProviderError(`Failed to get provider during transaction flow`) + } + + return provider +} + +async function getSigner(): Promise { + return (await getProvider()).getSigner() +} + +type SwapInfo = ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo +export function getSwapTransactionInfo(trade: ClassicTrade): SwapInfo +export function getSwapTransactionInfo(trade: UniswapXTrade): SwapInfo & { isUniswapXOrder: true } +export function getSwapTransactionInfo(trade: ClassicTrade | UniswapXTrade): SwapInfo { + const slippage = percentFromFloat(trade.slippageTolerance) + + return { + type: TransactionType.SWAP, + inputCurrencyId: currencyId(trade.inputAmount.currency), + outputCurrencyId: currencyId(trade.outputAmount.currency), + isUniswapXOrder: isUniswapX(trade), + ...(trade.tradeType === TradeType.EXACT_INPUT + ? { + tradeType: TradeType.EXACT_INPUT, + inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(), + expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(), + minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(slippage).quotient.toString(), + } + : { + tradeType: TradeType.EXACT_OUTPUT, + maximumInputCurrencyAmountRaw: trade.maximumAmountIn(slippage).quotient.toString(), + outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(), + expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(), + }), + } +} diff --git a/apps/web/src/state/sagas/transactions/wrapSaga.ts b/apps/web/src/state/sagas/transactions/wrapSaga.ts new file mode 100644 index 00000000000..06666bc4932 --- /dev/null +++ b/apps/web/src/state/sagas/transactions/wrapSaga.ts @@ -0,0 +1,80 @@ +import { Web3Provider } from '@ethersproject/providers' +import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +import { useEthersWeb3Provider } from 'hooks/useEthersProvider' +import { useCallback } from 'react' +import { useDispatch } from 'react-redux' +import { PopupType, addPopup } from 'state/application/reducer' +import { HandleOnChainStepParams, handleOnChainStep } from 'state/sagas/transactions/utils' +import { TransactionType, WrapTransactionInfo } from 'state/transactions/types' +import { call, put } from 'typed-redux-saga' +import { WrapCallback, WrapCallbackParams } from 'uniswap/src/features/transactions/swap/types/wrapCallback' +import { + TransactionStepType, + WrapTransactionStep, +} from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import { createSaga } from 'uniswap/src/utils/saga' +import { logger } from 'utilities/src/logger/logger' +import noop from 'utilities/src/react/noop' + +interface HandleWrapStepParams extends Omit, 'info'> {} +export function* handleWrapStep(params: HandleWrapStepParams) { + const info = getWrapTransactionInfo(params.step.amount) + return yield* call(handleOnChainStep, { ...params, info }) +} + +type WrapParams = WrapCallbackParams & { provider: Web3Provider } + +function* wrap(params: WrapParams) { + try { + const { account, inputCurrencyAmount, txRequest, provider } = params + + const step = { type: TransactionStepType.WrapTransaction, txRequest, amount: inputCurrencyAmount } as const + + const hash = yield* call(handleWrapStep, { + step, + account, + provider, + setCurrentStep: noop, + shouldWaitForConfirmation: false, + }) + + yield* put(addPopup({ content: { type: PopupType.Transaction, hash }, key: hash })) + + params.onSuccess() + } catch (error) { + logger.error(error, { tags: { file: 'wrapSaga', function: 'wrap' } }) + params.onFailure() + } +} + +function getWrapTransactionInfo(amount: CurrencyAmount): WrapTransactionInfo { + return amount.currency.isNative + ? { + type: TransactionType.WRAP, + unwrapped: false, + currencyAmountRaw: amount.quotient.toString(), + } + : { + type: TransactionType.WRAP, + unwrapped: true, + currencyAmountRaw: amount.quotient.toString(), + } +} + +export const wrapSaga = createSaga(wrap, 'wrap') + +export function useWrapCallback(): WrapCallback { + const appDispatch = useDispatch() + const provider = useEthersWeb3Provider() + + return useCallback( + (params: WrapCallbackParams) => { + if (!provider) { + throw new Error('Provider not found') + } + + appDispatch(wrapSaga.actions.trigger({ ...params, provider })) + }, + [appDispatch, provider], + ) +} diff --git a/apps/web/src/state/signatures/hooks.ts b/apps/web/src/state/signatures/hooks.ts index 023739294d6..23eff76e5b6 100644 --- a/apps/web/src/state/signatures/hooks.ts +++ b/apps/web/src/state/signatures/hooks.ts @@ -35,9 +35,12 @@ export function useOrder(orderHash: string): UniswapXOrderDetails | undefined { const order = signatures[orderHash] if ( !order || - ![SignatureType.SIGN_UNISWAPX_ORDER, SignatureType.SIGN_UNISWAPX_V2_ORDER, SignatureType.SIGN_LIMIT].includes( - order.type as SignatureType, - ) + ![ + SignatureType.SIGN_UNISWAPX_ORDER, + SignatureType.SIGN_UNISWAPX_V2_ORDER, + SignatureType.SIGN_LIMIT, + SignatureType.SIGN_PRIORITY_ORDER, + ].includes(order.type as SignatureType) ) { return undefined } @@ -102,7 +105,8 @@ function isPendingOrder(signature: SignatureDetails): signature is UniswapXOrder ].includes(signature.status) } else if ( signature.type === SignatureType.SIGN_UNISWAPX_ORDER || - signature.type === SignatureType.SIGN_UNISWAPX_V2_ORDER + signature.type === SignatureType.SIGN_UNISWAPX_V2_ORDER || + signature.type === SignatureType.SIGN_PRIORITY_ORDER ) { return [ UniswapXOrderStatus.OPEN, diff --git a/apps/web/src/state/signatures/parseRemote.ts b/apps/web/src/state/signatures/parseRemote.ts index 0befa375fb4..8d67ce84e6f 100644 --- a/apps/web/src/state/signatures/parseRemote.ts +++ b/apps/web/src/state/signatures/parseRemote.ts @@ -14,6 +14,7 @@ const SIGNATURE_TYPE_MAP: { [key in SwapOrderType]: SignatureType } = { [SwapOrderType.Limit]: SignatureType.SIGN_LIMIT, [SwapOrderType.Dutch]: SignatureType.SIGN_UNISWAPX_ORDER, [SwapOrderType.DutchV2]: SignatureType.SIGN_UNISWAPX_V2_ORDER, + // [SwapOrderType.Priority]: SignatureType.SIGN_PRIORITY_ORDER, } const ORDER_STATUS_MAP: { [key in SwapOrderStatus]: UniswapXOrderStatus } = { diff --git a/apps/web/src/state/signatures/types.ts b/apps/web/src/state/signatures/types.ts index 62aae21354a..1e74fb7fb65 100644 --- a/apps/web/src/state/signatures/types.ts +++ b/apps/web/src/state/signatures/types.ts @@ -14,12 +14,14 @@ export enum SignatureType { SIGN_UNISWAPX_ORDER = 'signUniswapXOrder', SIGN_UNISWAPX_V2_ORDER = 'signUniswapXV2Order', SIGN_LIMIT = 'signLimit', + SIGN_PRIORITY_ORDER = 'signPriorityOrder', } export const OFFCHAIN_ORDER_TYPE_TO_SIGNATURE_TYPE: Partial> = { [OffchainOrderType.DUTCH_AUCTION]: SignatureType.SIGN_UNISWAPX_ORDER, [OffchainOrderType.DUTCH_V2_AUCTION]: SignatureType.SIGN_UNISWAPX_V2_ORDER, [OffchainOrderType.LIMIT_ORDER]: SignatureType.SIGN_LIMIT, + [OffchainOrderType.PRIORITY_ORDER]: SignatureType.SIGN_PRIORITY_ORDER, } interface BaseSignatureFields { diff --git a/apps/web/src/state/swap/SwapContext.test.tsx b/apps/web/src/state/swap/SwapContext.test.tsx index 7f4d63e34e5..130350be02b 100644 --- a/apps/web/src/state/swap/SwapContext.test.tsx +++ b/apps/web/src/state/swap/SwapContext.test.tsx @@ -1,5 +1,4 @@ import { Percent } from '@uniswap/sdk-core' -import { Field } from 'components/swap/constants' import { SwapForm } from 'pages/Swap/SwapForm' import { SwapAndLimitContextProvider, SwapContextProvider } from 'state/swap/SwapContext' import { SwapAndLimitContext, SwapInfo } from 'state/swap/types' @@ -7,6 +6,7 @@ import { useSwapAndLimitContext, useSwapContext } from 'state/swap/useSwapContex import { render, screen } from 'test-utils/render' import { nativeOnChain } from 'uniswap/src/constants/tokens' import { UniverseChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' jest.mock('hooks/useContract', () => ({ @@ -33,12 +33,12 @@ describe('Swap Context', () => { allowedSlippage: new Percent(5, 1000), autoSlippage: new Percent(5, 1000), currencies: { - INPUT: undefined, - OUTPUT: undefined, + input: undefined, + output: undefined, }, currencyBalances: { - INPUT: undefined, - OUTPUT: undefined, + input: undefined, + output: undefined, }, inputError: expect.any(Object), inputTax: new Percent(0), @@ -54,7 +54,7 @@ describe('Swap Context', () => { }, setSwapState: expect.any(Function), swapState: { - independentField: 'INPUT', + independentField: 'input', typedValue: '', }, }) @@ -160,8 +160,8 @@ describe('Combined contexts', () => { // @ts-ignore rendering TestComponent sets derivedSwapInfo value expect(derivedSwapInfo?.currencies).toEqual({ - [Field.INPUT]: nativeOnChain(UniverseChainId.Mainnet), - [Field.OUTPUT]: undefined, + [CurrencyField.INPUT]: nativeOnChain(UniverseChainId.Mainnet), + [CurrencyField.OUTPUT]: undefined, }) }) }) diff --git a/apps/web/src/state/swap/SwapContext.tsx b/apps/web/src/state/swap/SwapContext.tsx index 08813ad57c2..810866cc7e6 100644 --- a/apps/web/src/state/swap/SwapContext.tsx +++ b/apps/web/src/state/swap/SwapContext.tsx @@ -1,6 +1,5 @@ import { Currency } from '@uniswap/sdk-core' -import { Field } from 'components/swap/constants' -import { useReportTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/TokenBalancesProvider' +import { useReportTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useReportTotalBalancesUsdForAnalytics' import { useAccount } from 'hooks/useAccount' import usePrevious from 'hooks/usePrevious' import { useUpdateAtom } from 'jotai/utils' @@ -10,6 +9,7 @@ import { useDerivedSwapInfo } from 'state/swap/hooks' import { CurrencyState, SwapAndLimitContext, SwapContext, SwapState, initialSwapState } from 'state/swap/types' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { InterfaceChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' import { areCurrenciesEqual } from 'uniswap/src/utils/currencyId' @@ -135,7 +135,7 @@ export function SwapContextProvider({ children, }: { initialTypedValue?: string - initialIndependentField?: Field + initialIndependentField?: CurrencyField multichainUXEnabled?: boolean children: React.ReactNode }) { diff --git a/apps/web/src/state/swap/hooks.test.ts b/apps/web/src/state/swap/hooks.test.ts index 2f671d17765..0c34748fb36 100644 --- a/apps/web/src/state/swap/hooks.test.ts +++ b/apps/web/src/state/swap/hooks.test.ts @@ -3,7 +3,7 @@ import { parse } from 'qs' import { queryParametersToCurrencyState, useInitialCurrencyState } from 'state/swap/hooks' import { ETH_MAINNET } from 'test-utils/constants' import { renderHook, waitFor } from 'test-utils/render' -import { MATIC_POLYGON, UNI } from 'uniswap/src/constants/tokens' +import { UNI, nativeOnChain } from 'uniswap/src/constants/tokens' import { UniverseChainId } from 'uniswap/src/types/chains' jest.mock('uniswap/src/features/gating/hooks', () => { @@ -186,7 +186,7 @@ describe('hooks', () => { { tokenBalances: [ { - token: MATIC_POLYGON, + token: nativeOnChain(UniverseChainId.Polygon), denominatedValue: { value: 1000, }, diff --git a/apps/web/src/state/swap/hooks.tsx b/apps/web/src/state/swap/hooks.tsx index f0c330d98f4..55ddd9e41f3 100644 --- a/apps/web/src/state/swap/hooks.tsx +++ b/apps/web/src/state/swap/hooks.tsx @@ -1,5 +1,5 @@ import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' -import { Field } from 'components/swap/constants' +import { ConnectWalletButtonText } from 'components/NavBar/accountCTAsExperimentUtils' import { CHAIN_IDS_TO_NAMES, useSupportedChainId } from 'constants/chains' import { NATIVE_CHAIN_ID } from 'constants/tokens' import { useCurrency, useCurrencyInfo } from 'hooks/Tokens' @@ -20,18 +20,17 @@ import { CurrencyState, SerializedCurrencyState, SwapInfo, SwapState } from 'sta import { useSwapAndLimitContext, useSwapContext } from 'state/swap/useSwapContext' import { useUserSlippageToleranceWithDefault } from 'state/user/hooks' import { useTokenProjects } from 'uniswap/src/features/dataApi/tokenProjects' -import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' -import { useExperimentGroupName } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { areCurrencyIdsEqual, currencyId } from 'uniswap/src/utils/currencyId' import { isAddress } from 'utilities/src/addresses' import { getParsedChainId } from 'utils/chains' export function useSwapActionHandlers(): { - onCurrencySelection: (field: Field, currency: Currency) => void + onCurrencySelection: (field: CurrencyField, currency?: Currency) => void onSwitchTokens: (options: { newOutputHasTax: boolean; previouslyEstimatedOutput: string }) => void - onUserInput: (field: Field, typedValue: string) => void + onUserInput: (field: CurrencyField, typedValue: string) => void } { const { swapState, setSwapState } = useSwapContext() const { currencyState, setCurrencyState } = useSwapAndLimitContext() @@ -44,31 +43,32 @@ export function useSwapActionHandlers(): { ) const onCurrencySelection = useCallback( - (field: Field, currency: Currency) => { + (field: CurrencyField, currency?: Currency) => { const [currentCurrencyKey, otherCurrencyKey]: (keyof CurrencyState)[] = - field === Field.INPUT ? ['inputCurrency', 'outputCurrency'] : ['outputCurrency', 'inputCurrency'] + field === CurrencyField.INPUT ? ['inputCurrency', 'outputCurrency'] : ['outputCurrency', 'inputCurrency'] const otherCurrency = currencyState[otherCurrencyKey] // the case where we have to swap the order - if (otherCurrency && currency.equals(otherCurrency)) { + if (otherCurrency && currency?.equals(otherCurrency)) { setCurrencyState({ [currentCurrencyKey]: currency, [otherCurrencyKey]: currencyState[currentCurrencyKey], }) setSwapState((swapState) => ({ ...swapState, - independentField: swapState.independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT, + independentField: + swapState.independentField === CurrencyField.INPUT ? CurrencyField.OUTPUT : CurrencyField.INPUT, })) // multichain ux case where we set input or output to different chain - } else if (otherCurrency?.chainId !== currency.chainId) { - const otherCurrencyTokenProjects = field === Field.INPUT ? outputTokenProjects : inputTokenProjects + } else if (currency && otherCurrency?.chainId !== currency.chainId) { + const otherCurrencyTokenProjects = field === CurrencyField.INPUT ? outputTokenProjects : inputTokenProjects const otherCurrency = otherCurrencyTokenProjects?.data?.find( - (project) => project?.currency.chainId === currency.chainId, + (project) => project?.currency.chainId === currency?.chainId, ) setCurrencyState((state) => ({ ...state, [currentCurrencyKey]: currency, [otherCurrencyKey]: - otherCurrency && !areCurrencyIdsEqual(currencyId(currency), otherCurrency.currencyId) + otherCurrency && currency && !areCurrencyIdsEqual(currencyId(currency), otherCurrency.currencyId) ? otherCurrency.currency : undefined, })) @@ -91,7 +91,7 @@ export function useSwapActionHandlers(): { previouslyEstimatedOutput: string }) => { // To prevent swaps with FOT tokens as exact-outputs, we leave it as an exact-in swap and use the previously estimated output amount as the new exact-in amount. - if (newOutputHasTax && swapState.independentField === Field.INPUT) { + if (newOutputHasTax && swapState.independentField === CurrencyField.INPUT) { setSwapState((swapState) => ({ ...swapState, typedValue: previouslyEstimatedOutput, @@ -99,7 +99,7 @@ export function useSwapActionHandlers(): { } else { setSwapState((prev) => ({ ...prev, - independentField: prev.independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT, + independentField: prev.independentField === CurrencyField.INPUT ? CurrencyField.OUTPUT : CurrencyField.INPUT, })) } @@ -112,7 +112,7 @@ export function useSwapActionHandlers(): { ) const onUserInput = useCallback( - (field: Field, typedValue: string) => { + (field: CurrencyField, typedValue: string) => { setSwapState((state) => { return { ...state, @@ -159,7 +159,7 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo { useMemo(() => [inputCurrency ?? undefined, outputCurrency ?? undefined], [inputCurrency, outputCurrency]), ) - const isExactIn: boolean = independentField === Field.INPUT + const isExactIn: boolean = independentField === CurrencyField.INPUT const parsedAmount = useMemo( () => tryParseCurrencyAmount(typedValue, (isExactIn ? inputCurrency : outputCurrency) ?? undefined), [inputCurrency, isExactIn, outputCurrency, typedValue], @@ -188,16 +188,16 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo { const currencyBalances = useMemo( () => ({ - [Field.INPUT]: relevantTokenBalances[0], - [Field.OUTPUT]: relevantTokenBalances[1], + [CurrencyField.INPUT]: relevantTokenBalances[0], + [CurrencyField.OUTPUT]: relevantTokenBalances[1], }), [relevantTokenBalances], ) - const currencies: { [field in Field]?: Currency } = useMemo( + const currencies: { [field in CurrencyField]?: Currency } = useMemo( () => ({ - [Field.INPUT]: inputCurrency, - [Field.OUTPUT]: outputCurrency, + [CurrencyField.INPUT]: inputCurrency, + [CurrencyField.OUTPUT]: outputCurrency, }), [inputCurrency, outputCurrency], ) @@ -220,25 +220,14 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo { isClassicTrade(trade.trade) && (nativeCurrencyBalanceUSD ?? 0) < (trade.trade.totalGasUseEstimateUSDWithBuffer ?? 0) const { isDisconnected } = useAccount() - const accountsCTAExperimentGroup = useExperimentGroupName(Experiments.AccountCTAs) - const isSignIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.SignInSignUp - const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount - const inputError = useMemo(() => { let inputError: ReactNode | undefined if (!account.isConnected) { - const disconnectedInputError = isSignIn ? ( - - ) : isLogIn ? ( - - ) : ( - - ) - inputError = isDisconnected ? disconnectedInputError : + inputError = isDisconnected ? : } - if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) { + if (!currencies[CurrencyField.INPUT] || !currencies[CurrencyField.OUTPUT]) { inputError = inputError ?? } @@ -258,7 +247,10 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo { } // compare input balance to max input based on version - const [balanceIn, maxAmountIn] = [currencyBalances[Field.INPUT], trade?.trade?.maximumAmountIn(allowedSlippage)] + const [balanceIn, maxAmountIn] = [ + currencyBalances[CurrencyField.INPUT], + trade?.trade?.maximumAmountIn(allowedSlippage), + ] if (balanceIn && maxAmountIn && balanceIn.lessThan(maxAmountIn)) { inputError = ( @@ -280,8 +272,6 @@ export function useDerivedSwapInfo(state: SwapState): SwapInfo { currencyBalances, trade?.trade, allowedSlippage, - isSignIn, - isLogIn, isDisconnected, nativeCurrency.symbol, ]) @@ -396,7 +386,7 @@ export function useInitialCurrencyState(): { initialInputCurrency?: Currency initialOutputCurrency?: Currency initialTypedValue?: string - initialField?: Field + initialField?: CurrencyField initialChainId: InterfaceChainId initialCurrencyLoading: boolean } { @@ -455,8 +445,8 @@ export function useInitialCurrencyState(): { const initialOutputCurrency = useCurrency(initialOutputCurrencyAddress, initialChainId) const initialTypedValue = initialInputCurrency || initialOutputCurrency ? parsedCurrencyState.value : undefined const initialField = - initialTypedValue && parsedCurrencyState.field && parsedCurrencyState.field in Field - ? Field[parsedCurrencyState.field as keyof typeof Field] + initialTypedValue && parsedCurrencyState.field && parsedCurrencyState.field in CurrencyField + ? CurrencyField[parsedCurrencyState.field as keyof typeof CurrencyField] : undefined return { diff --git a/apps/web/src/state/swap/types.ts b/apps/web/src/state/swap/types.ts index 66e80ee9b08..21a88263666 100644 --- a/apps/web/src/state/swap/types.ts +++ b/apps/web/src/state/swap/types.ts @@ -1,13 +1,13 @@ import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' -import { Field } from 'components/swap/constants' import { Dispatch, ReactNode, SetStateAction, createContext } from 'react' import { InterfaceTrade, RouterPreference, TradeState } from 'state/routing/types' import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' export type SwapInfo = { - currencies: { [field in Field]?: Currency } - currencyBalances: { [field in Field]?: CurrencyAmount } + currencies: { [field in CurrencyField]?: Currency } + currencyBalances: { [field in CurrencyField]?: CurrencyAmount } inputTax: Percent outputTax: Percent outputFeeFiatValue?: number @@ -44,7 +44,7 @@ export const EMPTY_DERIVED_SWAP_INFO: SwapInfo = Object.freeze({ export const initialSwapState: SwapState = { typedValue: '', - independentField: Field.INPUT, + independentField: CurrencyField.INPUT, } export const SwapContext = createContext({ @@ -113,7 +113,7 @@ export interface CurrencyState { } export interface SwapState { - readonly independentField: Field + readonly independentField: CurrencyField readonly typedValue: string routerPreferenceOverride?: RouterPreference.API } diff --git a/apps/web/src/test-utils/bundle-size-test.ts b/apps/web/src/test-utils/bundle-size-test.ts index 1e3500cf7ed..1ad4d34d269 100644 --- a/apps/web/src/test-utils/bundle-size-test.ts +++ b/apps/web/src/test-utils/bundle-size-test.ts @@ -32,8 +32,8 @@ const entryGzipSize = report.reduce( 0, ) -// somewhat arbitrary, based on current size (9/17/2024) -const limit = 2_165_000 +// somewhat arbitrary, based on current size (9/19/2024) +const limit = 2_185_000 if (entryGzipSize > limit) { console.error(`Bundle size has grown too big! Entry JS size is ${entryGzipSize}, over the limit of ${limit}.`) diff --git a/apps/web/src/theme/index.tsx b/apps/web/src/theme/index.tsx index d6827ae8d6b..b570a7ea807 100644 --- a/apps/web/src/theme/index.tsx +++ b/apps/web/src/theme/index.tsx @@ -9,8 +9,6 @@ import { useIsDarkMode } from 'theme/components/ThemeToggle' import { darkDeprecatedTheme, lightDeprecatedTheme } from 'theme/deprecatedColors' import { getAccent2, getNeutralContrast } from 'theme/utils' -export const NAV_HEIGHT = 72 - export const MEDIA_WIDTHS = { deprecated_upToExtraSmall: 500, deprecated_upToSmall: 720, diff --git a/apps/web/src/tracing/errors.ts b/apps/web/src/tracing/errors.ts index 3ddf42edec8..c051772e891 100644 --- a/apps/web/src/tracing/errors.ts +++ b/apps/web/src/tracing/errors.ts @@ -1,6 +1,6 @@ import { ClientOptions, ErrorEvent, EventHint } from '@sentry/types' import { ProviderRpcError } from '@web3-react/types' -import { wagmiConfig } from 'components/Web3Provider/wagmi' +import { wagmiConfig } from 'components/Web3Provider/wagmiConfig' import { didUserReject } from 'utils/swapErrorToUserReadableMessage' import { getAccount } from 'wagmi/actions' diff --git a/apps/web/src/types/position.d.ts b/apps/web/src/types/position.ts similarity index 84% rename from apps/web/src/types/position.d.ts rename to apps/web/src/types/position.ts index bce78b196ce..c0ccf926836 100644 --- a/apps/web/src/types/position.d.ts +++ b/apps/web/src/types/position.ts @@ -15,3 +15,8 @@ export interface PositionDetails { tokensOwed0: BigNumber tokensOwed1: BigNumber } + +export enum PositionField { + TOKEN0 = 'TOKEN0', + TOKEN1 = 'TOKEN1', +} diff --git a/apps/web/src/utils/chains.tsx b/apps/web/src/utils/chains.tsx index 5bb86f45347..eab8b8d7852 100644 --- a/apps/web/src/utils/chains.tsx +++ b/apps/web/src/utils/chains.tsx @@ -10,7 +10,7 @@ function getChainIdFromName(name: string) { export function getParsedChainId(parsedQs?: ParsedQs) { const chain = parsedQs?.chain if (!chain || typeof chain !== 'string') { - return + return undefined } return getChainIdFromName(chain) diff --git a/apps/web/src/utils/formatNumbers.test.ts b/apps/web/src/utils/formatNumbers.test.ts index 9153c077465..a0061539023 100644 --- a/apps/web/src/utils/formatNumbers.test.ts +++ b/apps/web/src/utils/formatNumbers.test.ts @@ -1,24 +1,20 @@ import { renderHook } from '@testing-library/react' import { CurrencyAmount, Percent } from '@uniswap/sdk-core' -import { useLocalCurrencyConversionRate } from 'graphql/data/ConversionRate' import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency' import { useActiveLocale } from 'hooks/useActiveLocale' import { mocked } from 'test-utils/mocked' import { USDC_MAINNET } from 'uniswap/src/constants/tokens' import { Currency } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { DEFAULT_LOCAL_CURRENCY, FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' +import { useAppFiatCurrency } from 'uniswap/src/features/fiatCurrency/hooks' import { Locale } from 'uniswap/src/features/language/constants' import { NumberType, useFormatter } from 'utils/formatNumbers' jest.mock('hooks/useActiveLocale') jest.mock('hooks/useActiveLocalCurrency') -jest.mock('graphql/data/ConversionRate') +jest.mock('uniswap/src/features/fiatCurrency/hooks') describe('formatNumber', () => { - beforeEach(() => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) - }) - it('formats token reference numbers correctly', () => { const { formatNumber } = renderHook(() => useFormatter()).result.current @@ -314,10 +310,6 @@ describe('formatNumber', () => { }) describe('formatUSDPrice', () => { - beforeEach(() => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) - }) - it('format fiat price correctly', () => { const { formatFiatPrice } = renderHook(() => useFormatter()).result.current @@ -354,10 +346,6 @@ describe('formatUSDPrice', () => { }) describe('formatPercent', () => { - beforeEach(() => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) - }) - it('should correctly format undefined', () => { const { formatPercent } = renderHook(() => useFormatter()).result.current @@ -387,10 +375,6 @@ describe('formatPercent', () => { }) describe('formatReviewSwapCurrencyAmount', () => { - beforeEach(() => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) - }) - it('should use TokenTx formatting under a default length', () => { const { formatReviewSwapCurrencyAmount } = renderHook(() => useFormatter()).result.current @@ -423,10 +407,6 @@ describe('formatReviewSwapCurrencyAmount', () => { }) describe('formatDelta', () => { - beforeEach(() => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) - }) - it.each([[null], [undefined], [Infinity], [NaN]])('should correctly format %p', (value) => { const { formatDelta } = renderHook(() => useFormatter()).result.current @@ -457,25 +437,15 @@ describe('formatDelta', () => { describe('formatToFiatAmount', () => { it('should return default values when undefined', () => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1, isLoading: false }) const { convertToFiatAmount } = renderHook(() => useFormatter()).result.current - expect(convertToFiatAmount()).toStrictEqual({ amount: 1.0, currency: DEFAULT_LOCAL_CURRENCY }) + expect(convertToFiatAmount(1)).toStrictEqual({ amount: 1.0, currency: DEFAULT_LOCAL_CURRENCY }) }) it('should return input amount for same currency', () => { - mocked(useActiveLocalCurrency).mockReturnValue(FiatCurrency.UnitedStatesDollar) - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1, isLoading: false }) + mocked(useAppFiatCurrency).mockReturnValue(FiatCurrency.UnitedStatesDollar) const { convertToFiatAmount } = renderHook(() => useFormatter()).result.current expect(convertToFiatAmount(12)).toStrictEqual({ amount: 12.0, currency: Currency.Usd }) }) - - it('should correctly convert different currency', () => { - mocked(useActiveLocalCurrency).mockReturnValue(FiatCurrency.CanadianDollar) - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 0.5, isLoading: false }) - const { convertToFiatAmount } = renderHook(() => useFormatter()).result.current - - expect(convertToFiatAmount(12)).toStrictEqual({ amount: 6.0, currency: Currency.Cad }) - }) }) diff --git a/apps/web/src/utils/formatNumbers.ts b/apps/web/src/utils/formatNumbers.ts index c90a4a90318..6733fa12442 100644 --- a/apps/web/src/utils/formatNumbers.ts +++ b/apps/web/src/utils/formatNumbers.ts @@ -1,13 +1,13 @@ import { formatEther as ethersFormatEther } from '@ethersproject/units' import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' import { getCurrencySymbolDisplayType } from 'constants/localCurrencies' -import { useLocalCurrencyConversionRate } from 'graphql/data/ConversionRate' import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency' import { useActiveLocale } from 'hooks/useActiveLocale' import usePrevious from 'hooks/usePrevious' import { useCallback, useMemo } from 'react' import { Bound } from 'state/mint/v3/actions' import { DEFAULT_LOCAL_CURRENCY, FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' +import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { DEFAULT_LOCALE, Locale } from 'uniswap/src/features/language/constants' type Nullish = T | null | undefined @@ -727,23 +727,6 @@ function formatReviewSwapCurrencyAmount(amount: CurrencyAmount, locale return formattedAmount } -function convertToFiatAmount( - amount = 1, - toCurrency = DEFAULT_LOCAL_CURRENCY, - conversionRate = 1, -): { amount: number; currency: FiatCurrency } { - const defaultResult = { amount, currency: DEFAULT_LOCAL_CURRENCY } - - if (defaultResult.currency === toCurrency) { - return defaultResult - } - - return { - amount: amount * conversionRate, - currency: toCurrency, - } -} - // TODO: https://linear.app/uniswap/issue/WEB-3495/import-useasyncdata-from-mobile type FiatCurrencyComponents = { groupingSeparator: string @@ -835,16 +818,13 @@ function handleFallbackCurrency( // Constructs an object that injects the correct locale and local currency into each of the above formatter functions. export function useFormatter() { const { formatterLocale, formatterLocalCurrency } = useFormatterLocales() - - const formatterLocalCurrencyIsUSD = formatterLocalCurrency === FiatCurrency.UnitedStatesDollar - const { data: localCurrencyConversionRate, isLoading: localCurrencyConversionRateIsLoading } = - useLocalCurrencyConversionRate(formatterLocalCurrency, formatterLocalCurrencyIsUSD) + const { convertFiatAmount, conversionRate: localCurrencyConversionRate } = useLocalizationContext() const previousSelectedCurrency = usePrevious(formatterLocalCurrency) const previousConversionRate = usePrevious(localCurrencyConversionRate) - const shouldFallbackToPrevious = !localCurrencyConversionRate && localCurrencyConversionRateIsLoading - const shouldFallbackToUSD = !localCurrencyConversionRate && !localCurrencyConversionRateIsLoading + const shouldFallbackToPrevious = !localCurrencyConversionRate + const shouldFallbackToUSD = !localCurrencyConversionRate const currencyToFormatWith = handleFallbackCurrency( formatterLocalCurrency, previousSelectedCurrency, @@ -948,11 +928,6 @@ export function useFormatter() { [currencyToFormatWith, formatterLocale], ) - const convertToFiatAmountWithLocales = useCallback( - (amount?: number) => convertToFiatAmount(amount, currencyToFormatWith, localCurrencyConversionRateToFormatWith), - [currencyToFormatWith, localCurrencyConversionRateToFormatWith], - ) - const formatConvertedFiatNumberOrString = useCallback( (options: Omit) => formatNumberOrString({ @@ -966,7 +941,7 @@ export function useFormatter() { return useMemo( () => ({ - convertToFiatAmount: convertToFiatAmountWithLocales, + convertToFiatAmount: convertFiatAmount, formatConvertedFiatNumberOrString, formatCurrencyAmount: formatCurrencyAmountWithLocales, formatEther: formatEtherwithLocales, @@ -980,7 +955,7 @@ export function useFormatter() { formatTickPrice: formatTickPriceWithLocales, }), [ - convertToFiatAmountWithLocales, + convertFiatAmount, formatConvertedFiatNumberOrString, formatCurrencyAmountWithLocales, formatDeltaWithLocales, diff --git a/apps/web/src/utils/getFetchPolicyForKey.ts b/apps/web/src/utils/getFetchPolicyForKey.ts deleted file mode 100644 index ca6b68bc55f..00000000000 --- a/apps/web/src/utils/getFetchPolicyForKey.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { WatchQueryFetchPolicy } from '@apollo/client' - -const keys = new Map() - -export const getFetchPolicyForKey = (key: string, expirationMs: number): WatchQueryFetchPolicy => { - const lastFetchTimestamp = keys.get(key) - const diffFromNow = lastFetchTimestamp ? Date.now() - lastFetchTimestamp : Number.MAX_SAFE_INTEGER - let fetchPolicy: WatchQueryFetchPolicy = 'cache-first' - - if (diffFromNow > expirationMs) { - keys.set(key, Date.now()) - fetchPolicy = 'network-only' - } - - return fetchPolicy -} diff --git a/apps/web/src/utils/prices.ts b/apps/web/src/utils/prices.ts index b5af386ab6a..83332c1d1a7 100644 --- a/apps/web/src/utils/prices.ts +++ b/apps/web/src/utils/prices.ts @@ -112,7 +112,7 @@ export function getPriceImpactWarning(priceImpact: Percent): 'warning' | 'error' if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_MEDIUM)) { return 'warning' } - return + return undefined } export function getPriceImpactColor(priceImpact: Percent): keyof DefaultTheme | undefined { diff --git a/apps/web/src/utils/transfer.ts b/apps/web/src/utils/transfer.ts index 70af6f4d74c..834052f3bb1 100644 --- a/apps/web/src/utils/transfer.ts +++ b/apps/web/src/utils/transfer.ts @@ -39,7 +39,7 @@ async function getTransferTransaction(transferInfo: TransferInfo): Promise - - - - - - + + + diff --git a/packages/ui/src/assets/icons/ellipsis.svg b/packages/ui/src/assets/icons/ellipsis.svg index 86dd1f50c57..f40f628bbc1 100644 --- a/packages/ui/src/assets/icons/ellipsis.svg +++ b/packages/ui/src/assets/icons/ellipsis.svg @@ -1,6 +1,5 @@ - + - diff --git a/packages/ui/src/assets/icons/triple-dots.svg b/packages/ui/src/assets/icons/triple-dots.svg deleted file mode 100644 index b0a623fe156..00000000000 --- a/packages/ui/src/assets/icons/triple-dots.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/ui/src/assets/index.ts b/packages/ui/src/assets/index.ts index d724f04a8d5..f072a7c08d3 100644 --- a/packages/ui/src/assets/index.ts +++ b/packages/ui/src/assets/index.ts @@ -34,6 +34,7 @@ export const ETH_LOGO = require('./logos/png/eth-logo.png') export const OPENSEA_LOGO = require('./logos/png/opensea-logo.png') export const ENS_LOGO = require('./logos/png/ens-logo.png') export const FROGGY = require('./graphics/froggy.png') +export const ACROSS_LOGO = require('./logos/png/across-logo.png') export const CEX_TRANSFER_MODAL_BG_LIGHT = require('./graphics/cex-transfer-modal-bg-light.png') export const CEX_TRANSFER_MODAL_BG_DARK = require('./graphics/cex-transfer-modal-bg-dark.png') diff --git a/packages/ui/src/assets/logos/png/across-logo.png b/packages/ui/src/assets/logos/png/across-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3eac46903fba5cf1d225d7aabfae16715a2d931a GIT binary patch literal 1214 zcmV;v1VQ_WP)wK~#7F%~?NB z6hRb!Z|;OcG&vJ3u<>X_LU9;BfcOot)&#AzxCkU#VFDylX(9*(R$^g|-$3{P#G@d9 z4bVt|#U%>{u-fU%x2U}d(ee-}~$At8u#t#&#JoDTtqAxHv*G6J7VBPO;0 zhqwu2jbAt|5OFDEnA35E#1c?C+n&@-+yjPF3s?H4qc)EuVC;CaP|M)5aFs!50cgHt zO#Uc}HCR}KSGDbt8e>0OMV`O`3iQs>$@XwqTcrSU8PniP7wR&Bpm*|R>9z_4x{&4X zd11=N@sZ;~ zmh0e7P+yz8O+0)3Dijyr!nM11KnHo7IE`0aoDk!82lNoG)X~v9i@g=Pb1Yymrd+P7 z`+wxGF6bz=CEVow-s6RErPJz|eJ3pt_VraSOlk@LvAX5g4$xT8NAC75E#wIdSrJPoTO2fjTqJl0wc8v?i|7GXLQiCI5Jui= zql-L&=d}vB3@|bSXo5N%j@~8?7;>&f0IlcI1Xw40a}HWSUWW*(CD;irCzJ@Y0!5R#o|~Y9 zyh?=oJ$|4 zRpcpjnD4yT+xW~Ajb;u0SmjD&3Ekugwq=8X6{^~}pLzt4dC)Y+q5bgOujJ%=9+>ay zUm{)8RzK66Q@M<%2gHQTr2>$U5zq#Ucs7@twF?)K zdqP4;w-R|(nb}TS1g9|G;Hyc9*N4ymM-1gzMXC}x6q@IYn2^9LB9~bfq5B@40_h-? clyoEh10*okSY%xlU;qFB07*qoM6N<$f_b7LY5)KL literal 0 HcmV?d00001 diff --git a/packages/ui/src/components/UniversalImage/UniversalImage.tsx b/packages/ui/src/components/UniversalImage/UniversalImage.tsx index cb692baa0ea..65ee119964e 100644 --- a/packages/ui/src/components/UniversalImage/UniversalImage.tsx +++ b/packages/ui/src/components/UniversalImage/UniversalImage.tsx @@ -19,6 +19,7 @@ export function UniversalImage({ fallback, fastImage = false, testID, + onLoad, allowLocalUri = false, }: UniversalImageProps): JSX.Element | null { // Allow calculation of fields as needed @@ -109,6 +110,7 @@ export function UniversalImage({ alignItems="center" backgroundColor={style?.image?.backgroundColor as ColorTokens} borderRadius={style?.image?.borderRadius} + verticalAlign={style?.image?.verticalAlign} height={size.height} overflow="hidden" testID={testID ? `svg-${testID}` : undefined} @@ -128,6 +130,7 @@ export function UniversalImage({ style={style?.image} testID={testID ? `img-${testID}` : undefined} uri={imageHttpUrl} + onLoad={onLoad} /> ) } diff --git a/packages/ui/src/components/UniversalImage/internal/PlainImage.native.tsx b/packages/ui/src/components/UniversalImage/internal/PlainImage.native.tsx index 37baae28de2..b9b1b2ab0e4 100644 --- a/packages/ui/src/components/UniversalImage/internal/PlainImage.native.tsx +++ b/packages/ui/src/components/UniversalImage/internal/PlainImage.native.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { Image } from 'react-native' import { PlainImageProps } from 'ui/src/components/UniversalImage/types' -export function PlainImage({ uri, size, fallback, resizeMode, style, testID }: PlainImageProps): JSX.Element { +export function PlainImage({ uri, size, fallback, resizeMode, style, testID, onLoad }: PlainImageProps): JSX.Element { const [hasError, setHasError] = useState(false) if (hasError && fallback) { @@ -20,6 +20,7 @@ export function PlainImage({ uri, size, fallback, resizeMode, style, testID }: P onError={() => { setHasError(true) }} + onLoad={onLoad} /> ) } diff --git a/packages/ui/src/components/UniversalImage/internal/PlainImage.tsx b/packages/ui/src/components/UniversalImage/internal/PlainImage.tsx index 94e932658d4..6771ab6eed7 100644 --- a/packages/ui/src/components/UniversalImage/internal/PlainImage.tsx +++ b/packages/ui/src/components/UniversalImage/internal/PlainImage.tsx @@ -1,37 +1,6 @@ -import { useState } from 'react' -import { PlainImageProps, UniversalImageResizeMode } from 'ui/src/components/UniversalImage/types' -import { Flex } from 'ui/src/components/layout/Flex' -import { isTestEnv } from 'utilities/src/environment/env' +import { PlainImageProps } from 'ui/src/components/UniversalImage/types' +import { PlatformSplitStubError } from 'utilities/src/errors' -export function PlainImage({ uri, size, fallback, resizeMode, style, testID }: PlainImageProps): JSX.Element { - const [hasError, setHasError] = useState(false) - - // TODO cover all cases better - const objectFit = - resizeMode === UniversalImageResizeMode.Contain || resizeMode === UniversalImageResizeMode.Cover - ? resizeMode - : 'contain' - - const imgElement = ( - { - setHasError(true) - }} - /> - ) - - if (hasError && fallback) { - return fallback - } - - // TODO(MOB-3485): remove test run special casing - if (isTestEnv()) { - return {imgElement} - } else { - return imgElement - } +export function PlainImage(_props: PlainImageProps): JSX.Element { + throw new PlatformSplitStubError('PlainImage') } diff --git a/packages/ui/src/components/UniversalImage/internal/PlainImage.web.tsx b/packages/ui/src/components/UniversalImage/internal/PlainImage.web.tsx new file mode 100644 index 00000000000..90b4bd3dc08 --- /dev/null +++ b/packages/ui/src/components/UniversalImage/internal/PlainImage.web.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react' +import { PlainImageProps, UniversalImageResizeMode } from 'ui/src/components/UniversalImage/types' +import { Flex } from 'ui/src/components/layout/Flex' +import { isTestEnv } from 'utilities/src/environment/env' + +export function PlainImage({ uri, size, fallback, resizeMode, style, testID, onLoad }: PlainImageProps): JSX.Element { + const [hasError, setHasError] = useState(false) + + // TODO cover all cases better + const objectFit = + resizeMode === UniversalImageResizeMode.Contain || resizeMode === UniversalImageResizeMode.Cover + ? resizeMode + : 'contain' + + const imgElement = ( + { + setHasError(true) + }} + onLoad={onLoad} + /> + ) + + if (hasError && fallback) { + return fallback + } + + // TODO(MOB-3485): remove test run special casing + if (isTestEnv()) { + return {imgElement} + } else { + return imgElement + } +} diff --git a/packages/ui/src/components/UniversalImage/types.ts b/packages/ui/src/components/UniversalImage/types.ts index 2682ed97841..01274f02d68 100644 --- a/packages/ui/src/components/UniversalImage/types.ts +++ b/packages/ui/src/components/UniversalImage/types.ts @@ -1,9 +1,10 @@ import type { ImageRequireSource } from 'react-native' -import { FlexProps } from 'ui/src/components/layout/Flex' +import type { FlexProps } from 'ui/src/components/layout/Flex' export interface UniversalImageStyle { backgroundColor?: string borderRadius?: number + verticalAlign?: FlexProps['verticalAlign'] } export enum UniversalImageResizeMode { @@ -39,6 +40,7 @@ export interface UniversalImageProps { fastImage?: boolean testID?: string allowLocalUri?: boolean + onLoad?: () => void } export interface PlainImageProps { @@ -48,6 +50,7 @@ export interface PlainImageProps { style?: UniversalImageStyle resizeMode?: UniversalImageResizeMode testID?: string + onLoad?: () => void } export type FastImageWrapperProps = PlainImageProps & { diff --git a/packages/ui/src/components/UniversalImage/utils.ts b/packages/ui/src/components/UniversalImage/utils.ts index c114edd6add..d920f2b2bd8 100644 --- a/packages/ui/src/components/UniversalImage/utils.ts +++ b/packages/ui/src/components/UniversalImage/utils.ts @@ -51,6 +51,7 @@ export function useSvgData(uri: string, autoplay = false): SvgData | undefined { return await fetchSVG(uri, autoplay, controllerRef.current.signal) } catch (error) { logger.error(error, { tags: { file: 'WebSvgUri', function: 'fetchSvg' }, extra: { uri } }) + return undefined } }, [autoplay, uri]) diff --git a/packages/ui/src/components/icons/Check.tsx b/packages/ui/src/components/icons/Check.tsx index 75f46c587d0..e2589206c71 100644 --- a/packages/ui/src/components/icons/Check.tsx +++ b/packages/ui/src/components/icons/Check.tsx @@ -1,4 +1,4 @@ -import { G, Path, Svg } from 'react-native-svg' +import { Line, Svg } from 'react-native-svg' // eslint-disable-next-line no-relative-import-paths/no-relative-import-paths import { createIcon } from '../factories/createIcon' @@ -6,13 +6,9 @@ import { createIcon } from '../factories/createIcon' export const [Check, AnimatedCheck] = createIcon({ name: 'Check', getIcon: (props) => ( - - - - - - + + + ), - defaultFill: '#9B9B9B', }) diff --git a/packages/ui/src/components/icons/Ellipsis.tsx b/packages/ui/src/components/icons/Ellipsis.tsx index 34fbbec0deb..ad262f84f15 100644 --- a/packages/ui/src/components/icons/Ellipsis.tsx +++ b/packages/ui/src/components/icons/Ellipsis.tsx @@ -6,7 +6,7 @@ import { createIcon } from '../factories/createIcon' export const [Ellipsis, AnimatedEllipsis] = createIcon({ name: 'Ellipsis', getIcon: (props) => ( - + + + + ) +} diff --git a/packages/ui/src/components/icons/TripleDots.tsx b/packages/ui/src/components/icons/TripleDots.tsx deleted file mode 100644 index 6fe639adbc8..00000000000 --- a/packages/ui/src/components/icons/TripleDots.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Path, Svg } from 'react-native-svg' - -// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths -import { createIcon } from '../factories/createIcon' - -export const [TripleDots, AnimatedTripleDots] = createIcon({ - name: 'TripleDots', - getIcon: (props) => ( - - - - - - ), -}) diff --git a/packages/ui/src/components/icons/Unitag.tsx b/packages/ui/src/components/icons/Unitag.tsx index 69d30a23003..5961cea86e5 100644 --- a/packages/ui/src/components/icons/Unitag.tsx +++ b/packages/ui/src/components/icons/Unitag.tsx @@ -12,10 +12,24 @@ function _Unitag({ size = '$icon.24' }: { size: IconSizeTokens | number }): JSX. const sizeNumber = typeof size === 'number' ? size : getTokenValue(size) const universalImageSize = { height: sizeNumber, width: sizeNumber } - return isDarkMode ? ( - - ) : ( - + const getUri = () => { + if (isDarkMode) { + return isMobileApp ? UNITAG_DARK : UNITAG_DARK_SMALL + } + return isMobileApp ? UNITAG_LIGHT : UNITAG_LIGHT_SMALL + } + + return ( + ) } diff --git a/packages/ui/src/components/icons/exported.ts b/packages/ui/src/components/icons/exported.ts index 1b87074dfcf..81b422f8923 100644 --- a/packages/ui/src/components/icons/exported.ts +++ b/packages/ui/src/components/icons/exported.ts @@ -203,7 +203,6 @@ export * from './Trash' export * from './TrashFilled' export * from './TrendDown' export * from './TrendUp' -export * from './TripleDots' export * from './UniswapLogo' export * from './UniswapX' export * from './UserSquare' diff --git a/packages/ui/src/components/icons/index.tsx b/packages/ui/src/components/icons/index.tsx index 3da096b59b6..d34e2a9a755 100644 --- a/packages/ui/src/components/icons/index.tsx +++ b/packages/ui/src/components/icons/index.tsx @@ -1,5 +1,6 @@ export { BackArrow } from './BackArrow' export { AnimatedCaretChange, Caret } from './Caret' +export { HeartWithFill } from './HeartWithFill' export { OSDynamicCloudIcon } from './OSDynamicCloudIcon' export { OnboardingUnicon } from './OnboardingUnicon' export { AnimatedQuestionInCircleFilled, QuestionInCircleFilled } from './QuestionInCircleFilled' diff --git a/packages/ui/src/components/menu/ContextMenu.tsx b/packages/ui/src/components/menu/ContextMenu.tsx index 9caee05ed64..7723b0c9f57 100644 --- a/packages/ui/src/components/menu/ContextMenu.tsx +++ b/packages/ui/src/components/menu/ContextMenu.tsx @@ -65,7 +65,7 @@ export function ContextMenu({ // Note: Overlay needs to be rendered in portal since parent transforms don't let fixed elements target the viewport // see: https://stackoverflow.com/a/15256339 return ( - + {/* OVERLAY */} {/* Conditional rendering needs to be used here instead of CSS so that portals aren't duplicated */} {showMenu && ( diff --git a/packages/ui/src/components/modal/AdaptiveWebModal.tsx b/packages/ui/src/components/modal/AdaptiveWebModal.tsx index 6740fe01781..17d9f4addf8 100644 --- a/packages/ui/src/components/modal/AdaptiveWebModal.tsx +++ b/packages/ui/src/components/modal/AdaptiveWebModal.tsx @@ -56,6 +56,7 @@ export function WebBottomSheet({ isOpen, onClose, children, ...rest }: ModalProp pb="$spacing16" pt="$spacing8" width="100%" + backgroundColor="$transparent" onMouseDown={() => setHandlePressed(true)} onMouseUp={() => setHandlePressed(false)} > diff --git a/packages/ui/src/components/swipeablecards/SwipeableCard.web.tsx b/packages/ui/src/components/swipeablecards/SwipeableCard.web.tsx index 73cc07b199e..c55ab3a7a0d 100644 --- a/packages/ui/src/components/swipeablecards/SwipeableCard.web.tsx +++ b/packages/ui/src/components/swipeablecards/SwipeableCard.web.tsx @@ -1,6 +1,58 @@ -import { SwipeableCardProps } from 'ui/src/components/swipeablecards/props' -import { NotImplementedError } from 'utilities/src/errors' +import { useEffect, useState } from 'react' +import { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated' +import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' +import { SWIPEABLE_CARD_Y_OFFSET, SwipeableCardProps } from 'ui/src/components/swipeablecards/props' -export function SwipeableCard(_props: SwipeableCardProps): JSX.Element { - throw new NotImplementedError('SwipeableCard') +function getScale(stackIndex: number): number { + return 1 - stackIndex * 0.025 +} + +// TODO WALL-4684 - Figure out how best way to swipe functionality for web/extension +export function SwipeableCard({ + children, + stackIndex, + cardHeight, + onPress, + onLayout, +}: SwipeableCardProps): JSX.Element { + const initialYOffset = stackIndex * SWIPEABLE_CARD_Y_OFFSET + const initialScale = getScale(stackIndex) + + const yOffset = useSharedValue(initialYOffset) + const scale = useSharedValue(initialScale) + const panOffset = useSharedValue(0) + + const [height, setHeight] = useState(0) + const [targetYOffset, setTargetYOffset] = useState(initialYOffset) + + useEffect(() => { + onLayout({ height, yOffset: targetYOffset }) + }, [height, onLayout, targetYOffset]) + + useEffect(() => { + const nextYOffset = stackIndex * SWIPEABLE_CARD_Y_OFFSET + + setTargetYOffset(nextYOffset) + yOffset.value = withSpring(nextYOffset) + scale.value = withSpring(getScale(stackIndex)) + panOffset.value = 0 + }, [panOffset, scale, stackIndex, yOffset]) + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [{ translateX: panOffset.value }, { translateY: yOffset.value }, { scale: scale.value }], + cursor: onPress ? 'pointer' : undefined, + } + }, [panOffset, scale, yOffset, onPress]) + + return ( + setHeight(event.nativeEvent.layout.height)} + onPress={onPress} + > + {children} + + ) } diff --git a/packages/ui/src/components/swipeablecards/SwipeableCardStack.web.tsx b/packages/ui/src/components/swipeablecards/SwipeableCardStack.web.tsx new file mode 100644 index 00000000000..7c50f3389cf --- /dev/null +++ b/packages/ui/src/components/swipeablecards/SwipeableCardStack.web.tsx @@ -0,0 +1,76 @@ +import { useCallback, useState } from 'react' +import { Flex } from 'ui/src/components/layout' +import { SwipeableCard } from 'ui/src/components/swipeablecards/SwipeableCard' +import { + SWIPEABLE_CARD_Y_OFFSET, + SwipeableCardProps, + SwipeableCardStackProps, +} from 'ui/src/components/swipeablecards/props' +import { usePrevious } from 'utilities/src/react/hooks' + +type PickedCardProps = Pick + +// TODO WALL-4684 After figuring out swipe for web, clean up duplicate code with the native version +export function SwipeableCardStack({ + cards, + minCardHeight = 0, + renderCard, + keyExtractor, + onSwiped, +}: SwipeableCardStackProps): JSX.Element { + const firstCard = cards[0] + const [activeKey, setActiveKey] = useState(firstCard ? keyExtractor(firstCard) : '') + + const [containerHeight, setContainerHeight] = useState(minCardHeight + (cards.length - 1) * SWIPEABLE_CARD_Y_OFFSET) + const [cardHeight, setCardHeight] = useState(minCardHeight) + + // Uses active key to track first card for when cards are removed + // If the active card is removed, the next card becomes active or will default to the first card + const keyIndex = cards.findIndex((card) => keyExtractor(card) === activeKey) + const prevIndex = usePrevious(keyIndex) + const activeIndex = keyIndex >= 0 ? keyIndex : prevIndex ? prevIndex + 1 : 0 + + const handleSwiped = useCallback( + (card: T, index: number) => { + const nextIndex = activeIndex === cards.length - 1 ? 0 : activeIndex + 1 + const nextCard = cards[nextIndex] + const nextKey = nextCard ? keyExtractor(nextCard) : '' + + setActiveKey(nextKey) + + onSwiped?.(card, index) + }, + [activeIndex, cards, keyExtractor, onSwiped], + ) + + const handleLayout = useCallback( + ({ height, yOffset }: { height: number; yOffset: number }) => { + setContainerHeight(Math.max(containerHeight, height + yOffset)) + setCardHeight(Math.max(cardHeight, height)) + }, + [cardHeight, containerHeight], + ) + + return ( + + {cards.map((card, index) => { + const stackIndex = (index - activeIndex + cards.length) % cards.length + + return ( + + handleSwiped(card, index)} + > + {renderCard(card, stackIndex)} + + + ) + })} + + ) +} diff --git a/packages/ui/src/components/text/Text.tsx b/packages/ui/src/components/text/Text.tsx index 770a62aedd7..e79a3fd2013 100644 --- a/packages/ui/src/components/text/Text.tsx +++ b/packages/ui/src/components/text/Text.tsx @@ -1,24 +1,10 @@ import { PropsWithChildren } from 'react' -import { GetProps, Text as TamaguiText, TextStyle, isWeb, styled } from 'tamagui' +import { GetProps, Text as TamaguiText, isWeb, styled } from 'tamagui' import { Flex } from 'ui/src/components/layout' import { HiddenFromScreenReaders } from 'ui/src/components/text/HiddenFromScreenReaders' import { useEnableFontScaling } from 'ui/src/components/text/useEnableFontScaling' import { Skeleton } from 'ui/src/loading/Skeleton' import { fonts } from 'ui/src/theme/fonts' -import { isAndroid } from 'utilities/src/platform' - -// android fonts appear too far down, tried includeFontPadding: false, but it seems to do nothing -// see: https://stackoverflow.com/questions/41525842/react-native-android-text-component-extra-padding -// instead setting a small negative y, which isn't beautiful but works -function getAndroidTextAdjustmentStyles(variant: keyof typeof fonts): TextStyle | null { - if (isAndroid) { - const fontSize = fonts[variant].fontSize - return { - y: -Math.round(fontSize * 0.036), - } - } - return null -} export const TextFrame = styled(TamaguiText, { fontFamily: '$body', @@ -32,7 +18,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$large', fontWeight: '$book', maxFontSizeMultiplier: fonts.heading1.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('heading1'), }, heading2: { fontFamily: '$heading', @@ -40,7 +25,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$medium', fontWeight: '$book', maxFontSizeMultiplier: fonts.heading2.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('heading2'), }, heading3: { fontFamily: '$heading', @@ -48,7 +32,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$small', fontWeight: '$book', maxFontSizeMultiplier: fonts.heading3.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('heading3'), }, subheading1: { fontFamily: '$subHeading', @@ -56,7 +39,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$large', fontWeight: '$book', maxFontSizeMultiplier: fonts.subheading1.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('subheading1'), }, subheading2: { fontFamily: '$subHeading', @@ -64,7 +46,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$small', fontWeight: '$book', maxFontSizeMultiplier: fonts.subheading2.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('subheading2'), }, body1: { fontFamily: '$body', @@ -72,7 +53,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$large', fontWeight: '$book', maxFontSizeMultiplier: fonts.body1.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('body1'), }, body2: { fontFamily: '$body', @@ -80,7 +60,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$medium', fontWeight: '$book', maxFontSizeMultiplier: fonts.body2.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('body2'), }, body3: { fontFamily: '$body', @@ -88,7 +67,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$small', fontWeight: '$book', maxFontSizeMultiplier: fonts.body3.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('body3'), }, body4: { fontFamily: '$body', @@ -96,7 +74,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$micro', fontWeight: '$book', maxFontSizeMultiplier: fonts.body4.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('body4'), }, buttonLabel1: { fontFamily: '$button', @@ -104,7 +81,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$large', fontWeight: '$medium', maxFontSizeMultiplier: fonts.buttonLabel1.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('buttonLabel1'), }, buttonLabel2: { fontFamily: '$button', @@ -112,7 +88,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$medium', fontWeight: '$medium', maxFontSizeMultiplier: fonts.buttonLabel2.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('buttonLabel1'), }, buttonLabel3: { fontFamily: '$button', @@ -120,7 +95,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$small', fontWeight: '$medium', maxFontSizeMultiplier: fonts.buttonLabel3.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('buttonLabel1'), }, buttonLabel4: { fontFamily: '$button', @@ -128,7 +102,6 @@ export const TextFrame = styled(TamaguiText, { lineHeight: '$micro', fontWeight: '$medium', maxFontSizeMultiplier: fonts.buttonLabel4.maxFontSizeMultiplier, - ...getAndroidTextAdjustmentStyles('buttonLabel2'), }, monospace: { fontFamily: '$body', diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 52c217f6972..8f8d723a930 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -36,6 +36,7 @@ export type { Adapt, CircleProps, ColorTokens, + GetProps, GetRef, InputProps, PopperProps, diff --git a/packages/ui/src/scripts/componentize-icons.ts b/packages/ui/src/scripts/componentize-icons.ts index eaeeb17e88d..75c27a354b9 100644 --- a/packages/ui/src/scripts/componentize-icons.ts +++ b/packages/ui/src/scripts/componentize-icons.ts @@ -82,6 +82,7 @@ async function generateSVGComponent(svg: string, fileName: string): Promise { if (!imageUrl) { - return + return undefined } const imageColors = await ImageColors.getColors(imageUrl, { @@ -44,5 +44,5 @@ export async function getExtractedColors( } } - return + return undefined } diff --git a/packages/uniswap/jest-setup.js b/packages/uniswap/jest-setup.js index f745d4cd185..be374e4588b 100644 --- a/packages/uniswap/jest-setup.js +++ b/packages/uniswap/jest-setup.js @@ -8,7 +8,7 @@ import { mockSharedPersistQueryClientProvider } from 'uniswap/src/test/mocks/moc jest.mock('react-native-localize', () => mockRNLocalize) -jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext) +jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext({})) // Use native modal jest.mock('uniswap/src/components/modals/Modal', () => { diff --git a/packages/uniswap/package.json b/packages/uniswap/package.json index e7dfaba5de4..7fe91882ec8 100644 --- a/packages/uniswap/package.json +++ b/packages/uniswap/package.json @@ -15,7 +15,8 @@ "lint:fix": "eslint . --ext ts,tsx --fix", "test": "jest --passWithNoTests", "tradingapi:schema": "curl https://api.uniswap.org/v2/trade/api.json -o ./src/data/tradingApi/api.json", - "tradingapi:generate": "openapi --input ./src/data/tradingApi/api.json --output ./src/data/tradingApi/__generated__ --client axios --useOptions --exportServices true --exportModels true", + "tradingapi:generate": "openapi --input ./src/data/tradingApi/api.json --output ./src/data/tradingApi/__generated__ --client axios --useOptions --exportServices true --exportModels true && yarn tradingapi:add-local-types", + "tradingapi:add-local-types": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./src/data/tradingApi/modifyTradingApiTypes.mts", "snapshots": "jest -u", "typecheck": "tsc -b" }, @@ -40,13 +41,13 @@ "@tanstack/react-query": "5.51.16", "@tanstack/react-query-persist-client": "5.51.23", "@typechain/ethers-v5": "7.2.0", - "@uniswap/analytics-events": "2.36.0", + "@uniswap/analytics-events": "2.37.0", "@uniswap/client-explore": "0.0.9", - "@uniswap/client-pools": "0.0.0", + "@uniswap/client-pools": "0.0.3", "@uniswap/permit2-sdk": "1.3.0", "@uniswap/router-sdk": "1.9.2", "@uniswap/sdk-core": "5.3.0", - "@uniswap/uniswapx-sdk": "^2.1.0-beta.8", + "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", "@uniswap/v2-sdk": "4.3.2", "@uniswap/v3-sdk": "3.14.0", "apollo-link-rest": "0.9.0", @@ -119,6 +120,7 @@ "jest-presets": "workspace:^", "react-dom": "18.2.0", "react-native-dotenv": "3.2.0", + "ts-morph": "23.0.0", "tsafe": "1.6.4", "typechain": "5.2.0", "typescript": "5.3.3" diff --git a/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx b/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx index 3bec6edadc4..099c6fb9f61 100644 --- a/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx +++ b/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx @@ -1,64 +1,23 @@ -import { useMemo } from 'react' -import { Flex, Separator, useExtractedTokenColor, useSporeColors } from 'ui/src' -import { Sign } from 'ui/src/components/icons/Sign' -import { Swap } from 'ui/src/components/icons/Swap' -import { DEP_accentColors, iconSizes } from 'ui/src/theme' -import { Step, StepDetails, StepStatus } from 'uniswap/src/components/ConfirmSwapModal/Step' -import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' -import { uniswapUrls } from 'uniswap/src/constants/urls' -import { Routing } from 'uniswap/src/data/tradingApi/__generated__' -import { useCurrencyInfo, useNativeCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' -import { ClassicTrade, UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade' -import { TransactionStep, TransactionStepType } from 'uniswap/src/features/transactions/swap/utils/generateSwapSteps' -import { t } from 'uniswap/src/i18n' -import { UniverseChainId } from 'uniswap/src/types/chains' -import { buildCurrencyId } from 'uniswap/src/utils/currencyId' +import { Flex, Separator } from 'ui/src' +import { + TokenApprovalTransactionStepRow, + TokenRevocationTransactionStepRow, +} from 'uniswap/src/components/ConfirmSwapModal/steps/Approve' +import { Permit2SignatureStepRow } from 'uniswap/src/components/ConfirmSwapModal/steps/Permit' +import { SwapTransactionStepRow } from 'uniswap/src/components/ConfirmSwapModal/steps/Swap' +import { WrapTransactionStepRow } from 'uniswap/src/components/ConfirmSwapModal/steps/Wrap' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' +import { + TransactionStep, + TransactionStepType, +} from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' -const IconWrapper = ({ children }: { children: JSX.Element }): JSX.Element => { - return ( - - {children} - - ) -} - -const SwapIcon = (): JSX.Element => ( - - - -) - -const SignIcon = (): JSX.Element => ( - - - -) - -export default function ProgressIndicator({ - steps, - currentStep, - trade, -}: { +interface ProgressIndicatorProps { steps: TransactionStep[] currentStep?: { step: TransactionStep; accepted: boolean } - trade: ClassicTrade | UniswapXTrade -}): JSX.Element | null { - const colors = useSporeColors() - const nativeCurrency = useNativeCurrencyInfo(trade?.inputAmount.currency.chainId) - const currencyId = buildCurrencyId( - trade?.inputAmount.currency.chainId as UniverseChainId, - trade?.inputAmount.currency.isToken - ? trade?.inputAmount.currency.address - : trade.inputAmount.currency.wrapped.address, - ) - const currencyInfo = useCurrencyInfo(currencyId) - const inputTokenColor = useExtractedTokenColor( - currencyInfo?.logoUrl, - trade?.inputAmount.currency.symbol, - /*background=*/ colors.surface1.val, - /*default=*/ colors.neutral3.val, - ) +} +export function ProgressIndicator({ currentStep, steps }: ProgressIndicatorProps): JSX.Element | null { function getStatus(targetStep: TransactionStep): StepStatus { const currentIndex = steps.findIndex((step) => step.type === currentStep?.step.type) const targetIndex = steps.indexOf(targetStep) @@ -71,74 +30,6 @@ export default function ProgressIndicator({ } } - const stepDetails: Record = useMemo( - () => ({ - [TransactionStepType.WrapTransaction]: { - icon: , - rippleColor: inputTokenColor.tokenColor ?? undefined, - previewTitle: t('common.wrap', { symbol: nativeCurrency?.currency.symbol }), - actionRequiredTitle: t('common.wrapIn', { symbol: nativeCurrency?.currency.symbol }), - inProgressTitle: t('common.wrappingToken', { symbol: nativeCurrency?.currency.symbol }), - learnMoreLinkText: t('common.whyWrap', { symbol: nativeCurrency?.currency.symbol }), - learnMoreLinkHref: uniswapUrls.helpArticleUrls.wethExplainer, - }, - [TransactionStepType.TokenRevocationTransaction]: { - icon: , - rippleColor: inputTokenColor.tokenColor ?? undefined, - previewTitle: t('common.resetLimit', { symbol: currencyInfo?.currency.symbol }), - actionRequiredTitle: t('common.resetLimitWallet', { symbol: currencyInfo?.currency.symbol }), - inProgressTitle: t('common.resettingLimit', { symbol: currencyInfo?.currency.symbol }), - }, - [TransactionStepType.TokenApprovalTransaction]: { - icon: , - rippleColor: inputTokenColor.tokenColor ?? undefined, - previewTitle: t('common.approveSpend', { symbol: currencyInfo?.currency.symbol }), - actionRequiredTitle: t('common.wallet.approve'), - inProgressTitle: t('common.approvePending'), - learnMoreLinkText: t('common.whyApprove'), - learnMoreLinkHref: uniswapUrls.helpArticleUrls.approvalsExplainer, - }, - [TransactionStepType.Permit2Signature]: { - icon: , - rippleColor: colors.accent1.val, - previewTitle: t('common.signMessage'), - actionRequiredTitle: t('common.signMessageWallet'), - learnMoreLinkText: t('common.whySign'), - learnMoreLinkHref: uniswapUrls.helpArticleUrls.approvalsExplainer, - }, - [TransactionStepType.SwapTransaction]: { - icon: , - rippleColor: DEP_accentColors.blue400, - previewTitle: t('swap.confirmSwap'), - actionRequiredTitle: t('common.confirmSwap'), - inProgressTitle: t('common.swapPending'), - ...(trade?.routing === Routing.DUTCH_V2 && { - timeToStart: trade.order.info.deadline - Math.floor(Date.now() / 1000), - delayedStartTitle: t('common.confirmTimedOut'), - }), - learnMoreLinkText: t('common.learnMoreSwap'), - learnMoreLinkHref: uniswapUrls.helpArticleUrls.howToSwapTokens, - }, - [TransactionStepType.SwapTransactionAsync]: { - icon: , - rippleColor: DEP_accentColors.blue400, - previewTitle: t('swap.confirmSwap'), - actionRequiredTitle: t('common.confirmSwap'), - inProgressTitle: t('common.swapPending'), - learnMoreLinkText: t('common.learnMoreSwap'), - learnMoreLinkHref: uniswapUrls.helpArticleUrls.howToSwapTokens, - }, - [TransactionStepType.UniswapXSignature]: { - icon: , - rippleColor: DEP_accentColors.blue400, - previewTitle: t('swap.confirmSwap'), - actionRequiredTitle: t('common.confirmSwap'), - inProgressTitle: t('common.swapPending'), - }, - }), - [trade, currencyInfo, inputTokenColor.tokenColor, nativeCurrency?.currency.symbol, colors.accent1], - ) - if (steps.length === 0) { return null } @@ -146,14 +37,29 @@ export default function ProgressIndicator({ return ( - {steps.map((step, i) => { - return ( - - - {i !== steps.length - 1 && } - - ) - })} + {steps.map((step, i) => ( + + + {i !== steps.length - 1 && } + + ))} ) } + +function Step({ step, status }: { step: TransactionStep; status: StepStatus }): JSX.Element { + switch (step.type) { + case TransactionStepType.WrapTransaction: + return + case TransactionStepType.TokenApprovalTransaction: + return + case TransactionStepType.TokenRevocationTransaction: + return + case TransactionStepType.Permit2Signature: + return + case TransactionStepType.SwapTransaction: + case TransactionStepType.SwapTransactionAsync: + case TransactionStepType.UniswapXSignature: + return + } +} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/Step.tsx b/packages/uniswap/src/components/ConfirmSwapModal/Step.tsx deleted file mode 100644 index 93b24e5e7b9..00000000000 --- a/packages/uniswap/src/components/ConfirmSwapModal/Step.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { ReactElement, useEffect, useState } from 'react' -import { Anchor, ColorTokens, Flex, SpinningLoader, Text } from 'ui/src' -import { Check } from 'ui/src/components/icons/Check' -import { PulseRipple } from 'ui/src/loading/PulseRipple' -import { fonts, iconSizes, spacing } from 'ui/src/theme' - -export interface StepDetails { - // Left-justified icon representing the step and grayed out when step is not active - icon: ReactElement - // Ripple animation around the icon of the currently active step (use color extraction to select) - rippleColor?: string - // Text shown before the step becomes active - previewTitle: string - // Text shown when the step is active and awaiting user input - actionRequiredTitle: string | ReactElement - // Text shown when user input has been accepted and step has yet to complete - inProgressTitle?: string - // Amount of time in seconds the user has to take action on a step (e.g. UniswapX exclusivity window) - timeToStart?: number - // Text shown when timeToStart is exceeded (countdown reaches zero) - delayedStartTitle?: string - // Anchor text displayed for the Learn-More link - learnMoreLinkText?: string - // URL for Learn-More link (opened in new tab) - learnMoreLinkHref?: string -} - -export enum StepStatus { - Preview, - Active, - InProgress, - Complete, -} - -function Icon({ - stepStatus, - icon, - rippleColor, -}: { - stepStatus: StepStatus - icon: ReactElement - rippleColor?: string -}): JSX.Element { - if (stepStatus === StepStatus.InProgress) { - return ( - - - - ) - } - return ( - - {stepStatus === StepStatus.Active && } - - {icon} - - - ) -} - -function Title({ - stepStatus, - stepDetails, - isTimeRemaining, -}: { - stepStatus: StepStatus - stepDetails: StepDetails - isTimeRemaining: boolean -}): JSX.Element | null { - switch (stepStatus) { - case StepStatus.Preview: - return ( - - {stepDetails.previewTitle} - - ) - case StepStatus.Active: - return ( - - {isTimeRemaining ? stepDetails.actionRequiredTitle : stepDetails.delayedStartTitle} - - ) - case StepStatus.InProgress: - return ( - - {isTimeRemaining ? stepDetails.inProgressTitle : null} - - ) - case StepStatus.Complete: - return ( - - {stepDetails.previewTitle} - - ) - default: - return null - } -} - -function Timer({ secondsRemaining }: { secondsRemaining: number }): JSX.Element { - const minutes = Math.floor(secondsRemaining / 60) - const seconds = secondsRemaining % 60 - const minutesText = minutes < 10 ? `0${minutes}` : minutes - const secondsText = seconds < 10 ? `0${seconds}` : seconds - const timerText = `${minutesText}:${secondsText}` - return ( - - {timerText} - - ) -} - -export function Step({ stepStatus, stepDetails }: { stepStatus: StepStatus; stepDetails: StepDetails }): JSX.Element { - // Timer is shown in two cases: - // (1) User has a specified amount of time to perform a required action. Timer starts running as soon as the step becomes active. - // (2) Step has an estimated amount of time in which it should be completed. Timer starts running when step is in progress. - const [secondsRemaining, setSecondsRemaining] = useState(null) - useEffect(() => { - if (stepStatus === StepStatus.Active && stepDetails?.timeToStart) { - setSecondsRemaining(stepDetails.timeToStart) - } else { - setSecondsRemaining(null) - return - } - - const timer = setInterval(() => { - setSecondsRemaining((prevSecondsRemaining) => { - if (prevSecondsRemaining && prevSecondsRemaining > 0) { - return prevSecondsRemaining - 1 - } - clearInterval(timer) - return 0 - }) - }, 1000) - - return (): void => clearInterval(timer) - }, [stepStatus, stepDetails.timeToStart]) - - return ( - - - - - 0} - stepDetails={stepDetails} - stepStatus={stepStatus} - /> - {stepStatus === StepStatus.Active && stepDetails.learnMoreLinkHref && stepDetails.learnMoreLinkText && ( - - {stepDetails.learnMoreLinkText} - - )} - - - {secondsRemaining !== null && } - {stepStatus === StepStatus.Complete && } - - ) -} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/steps/Approve.tsx b/packages/uniswap/src/components/ConfirmSwapModal/steps/Approve.tsx new file mode 100644 index 00000000000..cb3abcbc3bd --- /dev/null +++ b/packages/uniswap/src/components/ConfirmSwapModal/steps/Approve.tsx @@ -0,0 +1,53 @@ +import { useTranslation } from 'react-i18next' +import { StepRowProps, StepRowSkeleton } from 'uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { + TokenApprovalTransactionStep, + TokenRevocationTransactionStep, +} from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' + +export function TokenApprovalTransactionStepRow({ + step, + status, +}: StepRowProps): JSX.Element { + const { t } = useTranslation() + const { token } = step + const symbol = token.symbol + + const title = { + [StepStatus.Preview]: t('common.approveSpend', { symbol }), + [StepStatus.Active]: t('common.wallet.approve'), + [StepStatus.InProgress]: t('common.approvePending'), + [StepStatus.Complete]: t('common.approveSpend', { symbol }), + }[status] + + return ( + + ) +} + +export function TokenRevocationTransactionStepRow(props: StepRowProps): JSX.Element { + const { step, status } = props + + const { t } = useTranslation() + const { token } = step + const symbol = token.symbol + + const title = { + [StepStatus.Preview]: t('common.resetLimit', { symbol }), + [StepStatus.Active]: t('common.resetLimitWallet', { symbol }), + [StepStatus.InProgress]: t('common.resettingLimit', { symbol }), + [StepStatus.Complete]: t('common.resetLimit', { symbol }), + }[status] + + return +} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/steps/Permit.tsx b/packages/uniswap/src/components/ConfirmSwapModal/steps/Permit.tsx new file mode 100644 index 00000000000..214bd7ddaf3 --- /dev/null +++ b/packages/uniswap/src/components/ConfirmSwapModal/steps/Permit.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from 'react-i18next' +import { Flex, useSporeColors } from 'ui/src' +import { Sign } from 'ui/src/components/icons/Sign' +import { StepRowProps, StepRowSkeleton } from 'uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { Permit2SignatureStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' + +const SignIcon = (): JSX.Element => ( + + + +) + +export function Permit2SignatureStepRow({ status }: StepRowProps): JSX.Element { + const { t } = useTranslation() + const colors = useSporeColors() + + const title = status === StepStatus.Active ? t('common.signMessageWallet') : t('common.signMessage') + + return ( + } + learnMore={{ + url: uniswapUrls.helpArticleUrls.approvalsExplainer, + text: t('common.whySign'), + }} + rippleColor={colors.accent1.val} + status={status} + /> + ) +} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton.tsx b/packages/uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton.tsx new file mode 100644 index 00000000000..c80cd1c98f9 --- /dev/null +++ b/packages/uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton.tsx @@ -0,0 +1,120 @@ +import { Currency } from '@uniswap/sdk-core' +import { PropsWithChildren, useMemo } from 'react' +import { Anchor, ColorTokens, Flex, SpinningLoader, Text, useExtractedTokenColor, useSporeColors } from 'ui/src' +import { Check } from 'ui/src/components/icons/Check' +import { PulseRipple } from 'ui/src/loading/PulseRipple' +import { fonts, iconSizes, spacing } from 'ui/src/theme' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' +import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' +import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' +import { TransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import { currencyId } from 'uniswap/src/utils/currencyId' + +export interface StepRowProps { + step: TStepType + status: StepStatus +} + +interface StepRowSkeletonProps { + /** If passed, the step row icon will be the currency logo. */ + currency?: Currency + /** Icon to display if there is no currency to be displayed for this step. */ + icon?: JSX.Element + /** Color to display for the ripple effect around the icon or currency logo. This will default to a currency logo extracted color, if currency is defined. */ + rippleColor?: string + status: StepStatus + title: string + secondsRemaining?: number + learnMore?: { url: string; text: string } +} + +export function StepRowSkeleton(props: StepRowSkeletonProps): JSX.Element { + const { currency, icon, secondsRemaining, title, learnMore, status, rippleColor } = props + const colors = useSporeColors() + + const currencyInfo = useCurrencyInfo(currency ? currencyId(currency) : undefined) + const { tokenColor } = useExtractedTokenColor( + currencyInfo?.logoUrl, + currency?.symbol, + /*background=*/ colors.surface1.val, + /*default=*/ colors.neutral3.val, + ) + + const titleColor = status === StepStatus.Active || status === StepStatus.InProgress ? '$neutral1' : '$neutral2' + + return ( + + + + {icon ?? } + + + + {title} + + {status === StepStatus.Active && learnMore && ( + + {learnMore.text} + + )} + + + {!!secondsRemaining && } + {status === StepStatus.Complete && } + + ) +} + +function StepIconWrapper({ + children, + rippleColor, + stepStatus, +}: PropsWithChildren<{ + stepStatus: StepStatus + rippleColor?: string +}>): JSX.Element { + if (stepStatus === StepStatus.InProgress) { + return ( + + + + ) + } + return ( + + {stepStatus === StepStatus.Active && } + + {children} + + + ) +} + +function Timer({ secondsRemaining }: { secondsRemaining: number }): JSX.Element | null { + const timerText = useMemo(() => { + const minutes = Math.floor(secondsRemaining / 60) + const seconds = secondsRemaining % 60 + const minutesText = minutes < 10 ? `0${minutes}` : minutes + const secondsText = seconds < 10 ? `0${seconds}` : seconds + return `${minutesText}:${secondsText}` + }, [secondsRemaining]) + + return ( + + {timerText} + + ) +} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/steps/Swap.tsx b/packages/uniswap/src/components/ConfirmSwapModal/steps/Swap.tsx new file mode 100644 index 00000000000..f1f669aad26 --- /dev/null +++ b/packages/uniswap/src/components/ConfirmSwapModal/steps/Swap.tsx @@ -0,0 +1,88 @@ +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Flex, useSporeColors } from 'ui/src' +import { Swap } from 'ui/src/components/icons/Swap' +import { StepRowProps, StepRowSkeleton } from 'uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { + SwapTransactionStep, + SwapTransactionStepAsync, + TransactionStepType, + UniswapXSignatureStep, +} from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import noop from 'utilities/src/react/noop' + +const SwapIcon = (): JSX.Element => ( + + + +) + +type SwapSteps = SwapTransactionStep | SwapTransactionStepAsync | UniswapXSignatureStep +export function SwapTransactionStepRow({ step, status }: StepRowProps): JSX.Element { + const { t } = useTranslation() + const colors = useSporeColors() + + const deadline = step.type === TransactionStepType.UniswapXSignature ? step.deadline : undefined + const secondsRemaining = useSecondsUntilDeadline(deadline, status) + + const active = status === StepStatus.Active + const ranOutOfTimeTitle = active && deadline && !secondsRemaining ? t('common.confirmTimedOut') : undefined + + const title = + ranOutOfTimeTitle ?? + { + [StepStatus.Preview]: t('swap.confirmSwap'), + [StepStatus.Active]: t('common.confirmSwap'), + [StepStatus.InProgress]: t('common.swapPending'), + [StepStatus.Complete]: t('swap.confirmSwap'), + }[status] + + return ( + } + learnMore={{ + url: uniswapUrls.helpArticleUrls.howToSwapTokens, + text: t('common.learnMoreSwap'), + }} + rippleColor={colors.DEP_blue400.val} + status={status} + secondsRemaining={secondsRemaining} + /> + ) +} + +function useSecondsUntilDeadline(deadline: number | undefined, status: StepStatus): number | undefined { + const [secondsRemaining, setSecondsRemaining] = useState() + + useEffect(() => { + if (!deadline || status !== StepStatus.Active) { + setSecondsRemaining(undefined) + return noop + } + + const secondsUntilDeadline = deadline - Math.floor(Date.now() / 1000) + if (secondsUntilDeadline <= 0) { + return noop + } + + setSecondsRemaining(secondsUntilDeadline) + + const timer = setInterval(() => { + setSecondsRemaining((prevSecondsRemaining) => { + if (!prevSecondsRemaining) { + clearInterval(timer) + return prevSecondsRemaining + } + + return prevSecondsRemaining - 1 + }) + }, 1000) + + return () => clearInterval(timer) + }, [deadline, status]) + + return secondsRemaining +} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/steps/Wrap.tsx b/packages/uniswap/src/components/ConfirmSwapModal/steps/Wrap.tsx new file mode 100644 index 00000000000..b4b511fa408 --- /dev/null +++ b/packages/uniswap/src/components/ConfirmSwapModal/steps/Wrap.tsx @@ -0,0 +1,32 @@ +import { useTranslation } from 'react-i18next' +import { StepRowProps, StepRowSkeleton } from 'uniswap/src/components/ConfirmSwapModal/steps/StepRowSkeleton' +import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { WrapTransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' + +export function WrapTransactionStepRow({ step, status }: StepRowProps): JSX.Element { + const { t } = useTranslation() + + const { amount } = step + const { currency } = amount + const { symbol } = currency + + const title = { + [StepStatus.Active]: t('common.wrapIn', { symbol }), + [StepStatus.InProgress]: t('common.wrappingToken', { symbol }), + [StepStatus.Preview]: t('common.wrap', { symbol }), + [StepStatus.Complete]: t('common.wrap', { symbol }), + }[status] + + return ( + + ) +} diff --git a/packages/uniswap/src/components/ConfirmSwapModal/types.ts b/packages/uniswap/src/components/ConfirmSwapModal/types.ts new file mode 100644 index 00000000000..eb54268a266 --- /dev/null +++ b/packages/uniswap/src/components/ConfirmSwapModal/types.ts @@ -0,0 +1,6 @@ +export enum StepStatus { + Preview, + Active, + InProgress, + Complete, +} diff --git a/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx b/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx index 07c2988fbbc..b12cbed4ac7 100644 --- a/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx +++ b/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx @@ -19,7 +19,7 @@ import { errorShakeAnimation } from 'ui/src/animations/errorShakeAnimation' import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { useDynamicFontSizing } from 'ui/src/hooks/useDynamicFontSizing' -import { fonts } from 'ui/src/theme' +import { fonts, spacing } from 'ui/src/theme' import { AmountInput } from 'uniswap/src/components/CurrencyInputPanel/AmountInput' import { MaxAmountButton } from 'uniswap/src/components/CurrencyInputPanel/MaxAmountButton' import { SelectTokenButton } from 'uniswap/src/components/CurrencyInputPanel/SelectTokenButton' @@ -164,6 +164,8 @@ export const CurrencyInputPanel = memo( MIN_INPUT_FONT_SIZE, ) + const lineHeight = fontSize * 1.2 + // This is needed to ensure that the text resizes when modified from outside the component (e.g. custom numpad) useEffect(() => { if (value) { @@ -233,6 +235,7 @@ export const CurrencyInputPanel = memo( alignItems="center" justifyContent={!currencyInfo ? 'flex-end' : 'space-between'} py="$spacing8" + minHeight={MAX_INPUT_FONT_SIZE * 1.2 + 2 * spacing.spacing8} style={shakeStyle} > {isFiatMode && ( @@ -240,8 +243,7 @@ export const CurrencyInputPanel = memo( allowFontScaling color={showInsufficientBalanceWarning ? '$statusCritical' : color} fontSize={fontSize} - height={fontSize} - lineHeight={fontSize} + lineHeight={lineHeight} mr="$spacing4" > {fiatCurrencySymbol} @@ -281,10 +283,11 @@ export const CurrencyInputPanel = memo( // (the text input height is greater than the font size and the input is // centered vertically, so the caret is cut off but the text is not) fontSize={fontSize} + lineHeight={lineHeight} fontWeight="$book" maxDecimals={isFiatMode ? MAX_FIAT_INPUT_DECIMALS : currencyInfo.currency.decimals} maxFontSizeMultiplier={fonts.heading2.maxFontSizeMultiplier} - minHeight={2 * MAX_INPUT_FONT_SIZE} + minHeight={lineHeight} overflow="visible" placeholder="0" placeholderTextColor={colors.neutral3.val} @@ -327,7 +330,7 @@ export const CurrencyInputPanel = memo( - + {showInsufficientBalanceWarning && } {!hideCurrencyBalance && ( diff --git a/packages/uniswap/src/components/CurrencyLogo/CurrencyLogo.test.tsx b/packages/uniswap/src/components/CurrencyLogo/CurrencyLogo.test.tsx index 88494bfa7e1..a70caef7b1d 100644 --- a/packages/uniswap/src/components/CurrencyLogo/CurrencyLogo.test.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/CurrencyLogo.test.tsx @@ -3,6 +3,10 @@ import { ARBITRUM_DAI_CURRENCY_INFO, UNI_CURRENCY_INFO, arbitrumDaiCurrencyInfo import { renderWithProviders } from 'uniswap/src/test/render' import { render } from 'uniswap/src/test/test-utils' +jest.mock('ui/src/components/UniversalImage/internal/PlainImage', () => ({ + ...jest.requireActual('ui/src/components/UniversalImage/internal/PlainImage.web'), +})) + describe(CurrencyLogo, () => { it('renders without error', () => { const tree = render() diff --git a/packages/uniswap/src/components/CurrencyLogo/SplitLogo.test.tsx b/packages/uniswap/src/components/CurrencyLogo/SplitLogo.test.tsx index d76fdf6abe7..d47fdb43d2b 100644 --- a/packages/uniswap/src/components/CurrencyLogo/SplitLogo.test.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/SplitLogo.test.tsx @@ -3,6 +3,10 @@ import { DAI_CURRENCY_INFO, ETH_CURRENCY_INFO, daiCurrencyInfo, ethCurrencyInfo import { render, within } from 'uniswap/src/test/test-utils' import { UniverseChainId } from 'uniswap/src/types/chains' +jest.mock('ui/src/components/UniversalImage/internal/PlainImage', () => ({ + ...jest.requireActual('ui/src/components/UniversalImage/internal/PlainImage.web'), +})) + describe(SplitLogo, () => { it('renders without error', () => { const tree = render( diff --git a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx index ee2908fff87..f53e6057d0c 100644 --- a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx @@ -6,6 +6,10 @@ import { UniverseChainId } from 'uniswap/src/types/chains' // we silence the error logs to keep the test output clean. jest.mock('utilities/src/logger/logger') +jest.mock('ui/src/components/UniversalImage/internal/PlainImage', () => ({ + ...jest.requireActual('ui/src/components/UniversalImage/internal/PlainImage.web'), +})) + describe('TokenLogo', () => { it('renders without error', () => { const tree = render() diff --git a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx index 5dcb2114fdd..00075a5068f 100644 --- a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react' +import { memo, useState } from 'react' import { Flex, Text, UniversalImage, useColorSchemeFromSeed, useSporeColors } from 'ui/src' import { iconSizes, validColor } from 'ui/src/theme' import { STATUS_RATIO } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' @@ -24,6 +24,8 @@ export const TokenLogo = memo(function _TokenLogo({ hideNetworkLogo, networkLogoBorderWidth = 1.5, }: TokenLogoProps): JSX.Element { + const [showBackground, setShowBackground] = useState(false) + const colors = useSporeColors() const { foreground, background } = useColorSchemeFromSeed(name ?? symbol ?? '') @@ -69,17 +71,35 @@ export const TokenLogo = memo(function _TokenLogo({ size={{ height: size, width: size }} style={{ image: { - backgroundColor: colors.white.val, borderRadius: size / 2, }, }} testID="token-image" uri={url ?? undefined} + onLoad={() => setShowBackground(true)} /> ) return ( - + + {tokenImage} {showNetworkLogo && ( diff --git a/packages/uniswap/src/components/CurrencyLogo/__snapshots__/CurrencyLogo.test.tsx.snap b/packages/uniswap/src/components/CurrencyLogo/__snapshots__/CurrencyLogo.test.tsx.snap index d44a23de5fb..c953ec4db6d 100644 --- a/packages/uniswap/src/components/CurrencyLogo/__snapshots__/CurrencyLogo.test.tsx.snap +++ b/packages/uniswap/src/components/CurrencyLogo/__snapshots__/CurrencyLogo.test.tsx.snap @@ -8,11 +8,31 @@ exports[`CurrencyLogo renders a currency logo with network logo 1`] = ` "flexDirection": "column", "height": 40, "justifyContent": "center", + "position": "relative", "width": 40, } } testID="token-logo" > + + + + + , []) + + if (!isBridgingEnabled) { + return ( + + {suggestedTokens.map((token) => ( + + ))} + + ) + } + + return ( + token.currencyInfo.currencyId} + ItemSeparatorComponent={itemSeparatorComponent} + renderItem={({ item: token }) => ( + + )} + showsHorizontalScrollIndicator={false} + /> + ) +} diff --git a/packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.tsx b/packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.tsx new file mode 100644 index 00000000000..cf062a6f1b0 --- /dev/null +++ b/packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.tsx @@ -0,0 +1,13 @@ +import { OnSelectCurrency, TokenOption, TokenSection } from 'uniswap/src/components/TokenSelector/types' +import { PlatformSplitStubError } from 'utilities/src/errors' + +export type HorizontalTokenListProps = { + tokens: TokenOption[] + onSelectCurrency: OnSelectCurrency + index: number + section: TokenSection +} + +export function HorizontalTokenList(_props: HorizontalTokenListProps): JSX.Element { + throw new PlatformSplitStubError('TokenSectionBaseList') +} diff --git a/packages/uniswap/src/components/TokenSelector/renderSuggestedTokenItem.tsx b/packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.web.tsx similarity index 52% rename from packages/uniswap/src/components/TokenSelector/renderSuggestedTokenItem.tsx rename to packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.web.tsx index 74f2a273509..a04d89fb12e 100644 --- a/packages/uniswap/src/components/TokenSelector/renderSuggestedTokenItem.tsx +++ b/packages/uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList.web.tsx @@ -1,20 +1,15 @@ -import { Flex } from 'ui/src' +import { Flex } from 'ui/src/' +import { HorizontalTokenListProps } from 'uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList' import { SuggestedToken } from 'uniswap/src/components/TokenSelector/SuggestedToken' -import { OnSelectCurrency, TokenOption, TokenSection } from 'uniswap/src/components/TokenSelector/types' -export function renderSuggestedTokenItem({ - item: suggestedTokens, +export function HorizontalTokenList({ + tokens: suggestedTokens, + onSelectCurrency, index, section, - onSelectCurrency, -}: { - item: TokenOption[] - section: TokenSection - index: number - onSelectCurrency: OnSelectCurrency -}): JSX.Element { +}: HorizontalTokenListProps): JSX.Element { return ( - + {suggestedTokens.map((token) => ( setShowWarningModal(false)} + closeModalOnly={(): void => setShowWarningModal(false)} + onAcknowledge={onAcceptTokenWarning} /> ) diff --git a/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.web.tsx b/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.web.tsx index c6061b2f9af..dd3cc619cfb 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.web.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.web.tsx @@ -10,7 +10,8 @@ import { SectionRowInfo, TokenSectionBaseListProps, } from 'uniswap/src/components/TokenSelector/TokenSectionBaseList' -import { isSuggestedTokenSection } from 'uniswap/src/components/TokenSelector/TokenSelectorList' +import { isHorizontalListSection } from 'uniswap/src/components/TokenSelector/TokenSelectorList' +import { TokenOptionSection } from 'uniswap/src/components/TokenSelector/types' export const ITEM_SECTION_HEADER_ROW_HEIGHT = 40 const ITEM_ROW_HEIGHT = 68 @@ -27,9 +28,9 @@ function isSectionHeader(rowInfo: BaseListData): rowInfo is BaseListSectionRowIn return !('renderItem' in rowInfo) } -function isSuggestedTokenRowInfo(rowInfo: BaseListData): boolean { +function isHorizontalTokenRowInfo(rowInfo: BaseListData): boolean { const isHeader = isSectionHeader(rowInfo) - return !isHeader && isSuggestedTokenSection(rowInfo.section) + return !isHeader && isHorizontalListSection(rowInfo.section) } export function TokenSectionBaseList({ @@ -69,7 +70,7 @@ export function TokenSectionBaseList({ key: section.sectionKey, renderSectionHeader, } - if (!isSuggestedTokenSection(section)) { + if (section.sectionKey !== TokenOptionSection.SuggestedTokens) { acc.push(sectionInfo) } @@ -113,7 +114,7 @@ export function TokenSectionBaseList({ return 0 } - if (isSuggestedTokenRowInfo(item)) { + if (isHorizontalTokenRowInfo(item)) { if (!isSectionHeader(item)) { if (isArray(item.item) && !item.item.length) { return 0 diff --git a/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx b/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx index c870ad61cbc..7408ddb5689 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx @@ -11,10 +11,11 @@ import { TokenOptionSection } from 'uniswap/src/components/TokenSelector/types' export type TokenSectionHeaderProps = { sectionKey: TokenOptionSection rightElement?: JSX.Element + name?: string } -export function SectionHeader({ sectionKey, rightElement }: TokenSectionHeaderProps): JSX.Element | null { - const title = useTokenOptionsSectionTitle(sectionKey) +export function SectionHeader({ sectionKey, rightElement, name }: TokenSectionHeaderProps): JSX.Element | null { + const title = useTokenOptionsSectionTitle(sectionKey, name) const icon = getTokenOptionsSectionIcon(sectionKey) if (sectionKey === TokenOptionSection.SuggestedTokens) { return null @@ -32,7 +33,7 @@ export function SectionHeader({ sectionKey, rightElement }: TokenSectionHeaderPr ) } -export function useTokenOptionsSectionTitle(section: TokenOptionSection): string { +export function useTokenOptionsSectionTitle(section: TokenOptionSection, name?: string): string { const { t } = useTranslation() switch (section) { case TokenOptionSection.BridgingTokens: @@ -49,8 +50,10 @@ export function useTokenOptionsSectionTitle(section: TokenOptionSection): string return t('tokens.selector.section.search') case TokenOptionSection.SuggestedTokens: return '' // no suggested tokens header + case TokenOptionSection.SearchResultsByNetwork: + return name ?? '' default: - return '' + return section } } diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx index bc43d1b619e..e991f2525b4 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx @@ -13,12 +13,13 @@ import { TokenSelectorSwapInputList } from 'uniswap/src/components/TokenSelector import { TokenSelectorSwapOutputList } from 'uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList' import { flowToModalName } from 'uniswap/src/components/TokenSelector/flowToModalName' import { useFilterCallbacks } from 'uniswap/src/components/TokenSelector/hooks' -import { TokenSection, TokenSelectorFlow } from 'uniswap/src/components/TokenSelector/types' +import { TokenOptionSection, TokenSection, TokenSelectorFlow } from 'uniswap/src/components/TokenSelector/types' import PasteButton from 'uniswap/src/components/buttons/PasteButton' import { useBottomSheetContext } from 'uniswap/src/components/modals/BottomSheetContext' import { Modal } from 'uniswap/src/components/modals/Modal' import { NetworkFilter } from 'uniswap/src/components/network/NetworkFilter' import { useUniswapContext } from 'uniswap/src/contexts/UniswapContext' +import { TradeableAsset } from 'uniswap/src/entities/assets' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchTextInput } from 'uniswap/src/features/search/SearchTextInput' @@ -36,6 +37,7 @@ import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useDebounce } from 'utilities/src/time/timing' export const TOKEN_SELECTOR_WEB_MAX_WIDTH = 400 +export const TOKEN_SELECTOR_WEB_MAX_HEIGHT = 700 export enum TokenSelectorVariation { // used for Send flow, only show currencies with a balance @@ -54,17 +56,24 @@ export interface TokenSelectorProps { activeAccountAddress?: string chainId?: UniverseChainId chainIds?: UniverseChainId[] + input?: TradeableAsset isSurfaceReady?: boolean isLimits?: boolean onClose: () => void onSelectChain?: (chainId: UniverseChainId | null) => void - onSelectCurrency: (currency: Currency, currencyField: CurrencyField, context: SearchContext) => void + onSelectCurrency: ( + currency: Currency, + currencyField: CurrencyField, + context: SearchContext, + isBridgePair: boolean, + ) => void } export function TokenSelectorContent({ currencyField, flow, variation, + input, activeAccountAddress, chainId, chainIds = WALLET_SUPPORTED_CHAIN_IDS, @@ -140,7 +149,8 @@ export function TokenSelectorContent({ query: searchContext.query, }) - onSelectCurrency(currencyInfo.currency, currencyField, searchContext) + const isBridgePair = section.sectionKey === TokenOptionSection.BridgingTokens + onSelectCurrency(currencyInfo.currency, currencyField, searchContext, isBridgePair) }, [flow, page, currencyField, onSelectCurrency, debouncedSearchFilter], ) @@ -192,6 +202,7 @@ export function TokenSelectorContent({ isKeyboardOpen={isKeyboardOpen} parsedChainFilter={parsedChainFilter} searchFilter={searchFilter} + input={input} onSelectCurrency={onSelectCurrencyCallback} /> ) @@ -220,6 +231,7 @@ export function TokenSelectorContent({ case TokenSelectorVariation.SwapOutput: return ( ) } + + return undefined }, [ searchInFocus, searchFilter, @@ -239,6 +253,7 @@ export function TokenSelectorContent({ debouncedSearchFilter, parsedChainFilter, onSendEmptyActionPress, + input, ]) return ( @@ -329,6 +344,7 @@ function _TokenSelectorModal(props: TokenSelectorProps): JSX.Element { backgroundColor={colors.surface1.val} isModalOpen={isModalOpen} maxWidth={isWeb ? TOKEN_SELECTOR_WEB_MAX_WIDTH : undefined} + maxHeight={isInterface ? TOKEN_SELECTOR_WEB_MAX_HEIGHT : undefined} name={ModalName.TokenSelector} padding="$none" snapPoints={['65%', '100%']} diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx index 443310bd4d7..96efe0b5cf1 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import { AnimateTransition, Flex, Loader, Skeleton, Text } from 'ui/src' import { fonts } from 'ui/src/theme' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' +import { HorizontalTokenList } from 'uniswap/src/components/TokenSelector/HorizontalTokenList/HorizontalTokenList' import { TokenOptionItem } from 'uniswap/src/components/TokenSelector/TokenOptionItem' import { TokenSectionBaseList, @@ -10,8 +11,6 @@ import { } from 'uniswap/src/components/TokenSelector/TokenSectionBaseList' import { ITEM_SECTION_HEADER_ROW_HEIGHT } from 'uniswap/src/components/TokenSelector/TokenSectionBaseList.web' import { SectionHeader, TokenSectionHeaderProps } from 'uniswap/src/components/TokenSelector/TokenSectionHeader' -import { renderSuggestedTokenItem } from 'uniswap/src/components/TokenSelector/renderSuggestedTokenItem' -import { suggestedTokensKeyExtractor } from 'uniswap/src/components/TokenSelector/suggestedTokensKeyExtractor' import { OnSelectCurrency, TokenOption, @@ -25,11 +24,21 @@ import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyId } from 'uniswap/src/types/currency' import { NumberType } from 'utilities/src/format/types' -function isSuggestedTokenItem(data: TokenOption | TokenOption[]): data is TokenOption[] { +function isHorizontalListTokenItem( + data: TokenOption | TokenOption[], + section?: TokenSection, + chainFilter?: UniverseChainId | null, +): data is TokenOption[] { + if (section?.sectionKey === TokenOptionSection.BridgingTokens) { + return !chainFilter && Array.isArray(data) + } return Array.isArray(data) } -export function isSuggestedTokenSection(section: TokenSection): boolean { +export function isHorizontalListSection(section: TokenSection, chainFilter?: UniverseChainId | null): boolean { + if (section.sectionKey === TokenOptionSection.BridgingTokens) { + return !chainFilter + } return section.sectionKey === TokenOptionSection.SuggestedTokens } @@ -132,11 +141,11 @@ function _TokenSelectorList({ const renderItem = useCallback( ({ item, section, index }: { item: TokenOption | TokenOption[]; section: TokenSection; index: number }) => { - if (isSuggestedTokenItem(item) && isSuggestedTokenSection(section)) { - return renderSuggestedTokenItem({ item, section, index, onSelectCurrency }) + if (isHorizontalListTokenItem(item, section, chainFilter) && isHorizontalListSection(section)) { + return } - if (!isSuggestedTokenItem(item) && !isSuggestedTokenSection(section)) { + if (!isHorizontalListTokenItem(item, section, chainFilter)) { return ( ( - + ), [], ) @@ -205,8 +214,8 @@ function _TokenSelectorList({ } function key(item: TokenOption | TokenOption[]): CurrencyId { - if (isSuggestedTokenItem(item)) { - return suggestedTokensKeyExtractor(item) + if (isHorizontalListTokenItem(item)) { + return item.map((token) => token.currencyInfo.currencyId).join('-') } return item.currencyInfo.currencyId diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelectorSearchResultsList.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelectorSearchResultsList.tsx index 3ef2e3fdd89..e5594f1cecd 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelectorSearchResultsList.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelectorSearchResultsList.tsx @@ -5,6 +5,7 @@ import { SectionHeader } from 'uniswap/src/components/TokenSelector/TokenSection import { TokenSelectorList } from 'uniswap/src/components/TokenSelector/TokenSelectorList' import { useAddToSearchHistory, useTokenSectionsForSearchResults } from 'uniswap/src/components/TokenSelector/hooks' import { OnSelectCurrency, TokenOptionSection } from 'uniswap/src/components/TokenSelector/types' +import { TradeableAsset } from 'uniswap/src/entities/assets' import { UniverseChainId } from 'uniswap/src/types/chains' function EmptyResults({ searchFilter }: { searchFilter: string }): JSX.Element { @@ -32,6 +33,7 @@ function _TokenSelectorSearchResultsList({ debouncedParsedSearchFilter, isBalancesOnlySearch, isKeyboardOpen, + input, }: { onSelectCurrency: OnSelectCurrency activeAccountAddress?: string @@ -42,6 +44,7 @@ function _TokenSelectorSearchResultsList({ debouncedParsedSearchFilter: string | null isBalancesOnlySearch: boolean isKeyboardOpen?: boolean + input: TradeableAsset | undefined }): JSX.Element { const { t } = useTranslation() const { registerSearch } = useAddToSearchHistory() @@ -55,6 +58,7 @@ function _TokenSelectorSearchResultsList({ chainFilter ?? parsedChainFilter, debouncedParsedSearchFilter ?? debouncedSearchFilter, isBalancesOnlySearch, + input, ) const onSelectCurrency: OnSelectCurrency = (currencyInfo, section, index) => { diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapInputList.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapInputList.tsx index 020a8f3aec5..7d46fe94532 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapInputList.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapInputList.tsx @@ -82,7 +82,7 @@ function useTokenSectionsForSwapInput({ const sections = useMemo(() => { if (isSwapListLoading(loading, portfolioSection, popularSection)) { - return + return undefined } return [ diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx index cfdb1b3505b..cdfcdc5dcf8 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, useMemo } from 'react' import { TokenSelectorList } from 'uniswap/src/components/TokenSelector/TokenSelectorList' import { + useBridgingTokensOptions, useCommonTokensOptionsWithFallback, useFavoriteTokensOptions, usePopularTokensOptions, @@ -19,13 +20,18 @@ import { useTokenOptionsSection, } from 'uniswap/src/components/TokenSelector/utils' import { GqlResult } from 'uniswap/src/data/types' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { UniverseChainId } from 'uniswap/src/types/chains' import { isMobileApp } from 'utilities/src/platform' function useTokenSectionsForSwapOutput({ activeAccountAddress, chainFilter, + input, }: TokenSectionsHookProps): GqlResult { + const isBridgingEnabled = useFeatureFlag(FeatureFlags.Bridging) + const { data: portfolioTokenOptions, error: portfolioTokenOptionsError, @@ -56,26 +62,42 @@ function useTokenSectionsForSwapOutput({ // if there is no chain filter then we show mainnet tokens } = useCommonTokensOptionsWithFallback(activeAccountAddress, chainFilter ?? UniverseChainId.Mainnet) + const { + data: bridgingTokenOptions, + error: bridgingTokenOptionsError, + refetch: refetchBridgingTokenOptions, + loading: bridgingTokenOptionsLoading, + } = useBridgingTokensOptions({ input, walletAddress: activeAccountAddress, chainFilter }) + const recentlySearchedTokenOptions = useRecentlySearchedTokens(chainFilter) const error = (!portfolioTokenOptions && portfolioTokenOptionsError) || (!popularTokenOptions && popularTokenOptionsError) || (!favoriteTokenOptions && favoriteTokenOptionsError) || - (!commonTokenOptions && commonTokenOptionsError) + (!commonTokenOptions && commonTokenOptionsError) || + (!bridgingTokenOptions && bridgingTokenOptionsError) const loading = portfolioTokenOptionsLoading || popularTokenOptionsLoading || favoriteTokenOptionsLoading || - commonTokenOptionsLoading + commonTokenOptionsLoading || + bridgingTokenOptionsLoading const refetchAll = useCallback(() => { refetchPortfolioTokenOptions?.() refetchPopularTokenOptions?.() refetchFavoriteTokenOptions?.() refetchCommonTokenOptions?.() - }, [refetchCommonTokenOptions, refetchFavoriteTokenOptions, refetchPopularTokenOptions, refetchPortfolioTokenOptions]) + refetchBridgingTokenOptions?.() + }, [ + refetchBridgingTokenOptions, + refetchCommonTokenOptions, + refetchFavoriteTokenOptions, + refetchPopularTokenOptions, + refetchPortfolioTokenOptions, + ]) // we draw the Suggested pills as a single item of a section list, so `data` is TokenOption[][] const suggestedSection = useTokenOptionsSection(TokenOptionSection.SuggestedTokens, [commonTokenOptions ?? []]) @@ -85,14 +107,16 @@ function useTokenSectionsForSwapOutput({ const popularMinusPortfolioTokens = tokenOptionDifference(popularTokenOptions, portfolioTokenOptions) const popularSection = useTokenOptionsSection(TokenOptionSection.PopularTokens, popularMinusPortfolioTokens) + const bridgingSection = useTokenOptionsSection(TokenOptionSection.BridgingTokens, [bridgingTokenOptions ?? []]) const sections = useMemo(() => { if (isSwapListLoading(loading, portfolioSection, popularSection)) { - return + return undefined } return [ ...(suggestedSection ?? []), + ...(isBridgingEnabled ? bridgingSection ?? [] : []), ...(portfolioSection ?? []), ...(recentSection ?? []), // TODO(WEB-3061): Favorited wallets/tokens @@ -100,7 +124,16 @@ function useTokenSectionsForSwapOutput({ ...(isMobileApp ? favoriteSection ?? [] : []), ...(popularSection ?? []), ] - }, [favoriteSection, loading, popularSection, portfolioSection, recentSection, suggestedSection]) + }, [ + loading, + portfolioSection, + popularSection, + suggestedSection, + isBridgingEnabled, + bridgingSection, + recentSection, + favoriteSection, + ]) return useMemo( () => ({ @@ -118,6 +151,7 @@ function _TokenSelectorSwapOutputList({ activeAccountAddress, chainFilter, isKeyboardOpen, + input, }: TokenSectionsHookProps & { onSelectCurrency: OnSelectCurrency chainFilter: UniverseChainId | null @@ -130,6 +164,7 @@ function _TokenSelectorSwapOutputList({ } = useTokenSectionsForSwapOutput({ activeAccountAddress, chainFilter, + input, }) return ( { export function useCurrencies(currencyIds: string[]): GqlResult { const { data: baseCurrencyInfos, loading, error, refetch } = useTokenProjects(currencyIds) - const persistedError = usePersistedError(loading, error) + const persistedError = usePersistedError(loading, error instanceof ApolloError ? error : undefined) // TokenProjects returns tokens on every network, so filter out native assets that have a // bridged version on other networks @@ -176,7 +195,7 @@ export function useFavoriteCurrencies(): GqlResult { const favoriteCurrencyIds = useSelector(selectFavoriteTokens) const { data: favoriteTokensOnAllChains, loading, error, refetch } = useTokenProjects(favoriteCurrencyIds) - const persistedError = usePersistedError(loading, error) + const persistedError = usePersistedError(loading, error instanceof ApolloError ? error : undefined) // useTokenProjects returns each token on Arbitrum, Optimism, Polygon, // so we need to filter out the tokens which user has actually favorited @@ -315,6 +334,72 @@ export function useTokenSectionsForEmptySearch(chainFilter: UniverseChainId | nu ) } +export function useBridgingTokensOptions({ + input, + walletAddress, + chainFilter, +}: { + input: TradeableAsset | undefined + walletAddress: Address | undefined + chainFilter: UniverseChainId | null +}): GqlResult { + const isBridgingEnabled = useFeatureFlag(FeatureFlags.Bridging) + + const tokenIn = input?.address ? getTokenAddressFromChainForTradingApi(input.address, input.chainId) : undefined + const tokenInChainId = toTradingApiSupportedChainId(input?.chainId) + + const { + data: swappableTokens, + isLoading: loadingSwappableTokens, + error: errorSwappableTokens, + refetch: refetchSwappableTokens, + } = useTradingApiSwappableTokensQuery({ + params: + tokenIn && tokenInChainId && isBridgingEnabled + ? { + tokenIn, + tokenInChainId, + } + : undefined, + }) + + // Get portfolio balance for returned tokens + const { + data: portfolioBalancesById, + error: portfolioBalancesByIdError, + refetch: portfolioBalancesByIdRefetch, + loading: loadingPorfolioBalancesById, + } = usePortfolioBalancesForAddressById(isBridgingEnabled ? walletAddress : undefined) + + const tokenOptions = useSwappableTokensToTokenOptions(swappableTokens?.tokens, portfolioBalancesById) + const filteredTokenOptions = useMemo(() => filter(tokenOptions ?? null, chainFilter), [chainFilter, tokenOptions]) + + const error = (!portfolioBalancesById && portfolioBalancesByIdError) || (!tokenOptions && errorSwappableTokens) + + const refetch = useCallback(async () => { + if (isBridgingEnabled) { + portfolioBalancesByIdRefetch?.() + await refetchSwappableTokens?.() + } + }, [portfolioBalancesByIdRefetch, refetchSwappableTokens, isBridgingEnabled]) + + if (!isBridgingEnabled) { + return { + data: undefined, + loading: false, + error: undefined, + refetch: undefined, + } + } + + return { + data: filteredTokenOptions, + loading: loadingSwappableTokens || loadingPorfolioBalancesById, + error: error || undefined, + refetch, + } +} + export function useCurrencyInfosToTokenOptions({ currencyInfos, portfolioBalancesById, @@ -345,7 +430,48 @@ export function useCurrencyInfosToTokenOptions({ }, [currencyInfos, portfolioBalancesById, sortAlphabetically]) } -function useCommonTokensOptions( +export function useSwappableTokensToTokenOptions( + swappableTokens: GetSwappableTokensResponse['tokens'] | undefined, + portfolioBalancesById?: Record, +): TokenOption[] | undefined { + return useMemo(() => { + if (!swappableTokens) { + return undefined + } + + // We sort the tokens by chain in the same order chains in the network selector + const chainOrder = WALLET_SUPPORTED_CHAIN_IDS + const sortedSwappableTokens = [...swappableTokens].sort((a, b) => { + if (!a || !b) { + return 0 + } + const chainIdA = toSupportedChainId(a.chainId) + const chainIdB = toSupportedChainId(b.chainId) + if (!chainIdA || !chainIdB) { + return 0 + } + return chainOrder.indexOf(chainIdA) - chainOrder.indexOf(chainIdB) + }) + + return sortedSwappableTokens + .map((token) => { + const chainId = toSupportedChainId(token.chainId) + const validInput = token.address && token.chainId && portfolioBalancesById + if (!chainId || !validInput) { + return undefined + } + + const isNative = token.address === NATIVE_ADDRESS_FOR_TRADING_API + return ( + portfolioBalancesById[isNative ? buildNativeCurrencyId(chainId) : buildCurrencyId(chainId, token.address)] ?? + createEmptyTokenOptionFromSwappableToken(token) + ) + }) + .filter((tokenOption): tokenOption is TokenOption => tokenOption !== undefined) + }, [swappableTokens, portfolioBalancesById]) +} + +export function useCommonTokensOptions( address: Address | undefined, chainFilter: UniverseChainId | null, ): GqlResult { @@ -559,7 +685,11 @@ export function useTokenSectionsForSearchResults( chainFilter: UniverseChainId | null, searchFilter: string | null, isBalancesOnlySearch: boolean, + input: TradeableAsset | undefined, ): GqlResult { + const { t } = useTranslation() + const isBridgingEnabled = useFeatureFlag(FeatureFlags.Bridging) + const { data: portfolioBalancesById, error: portfolioBalancesByIdError, @@ -574,6 +704,14 @@ export function useTokenSectionsForSearchResults( loading: portfolioTokenOptionsLoading, } = usePortfolioTokenOptions(address, chainFilter, searchFilter ?? undefined) + // Bridging tokens are only shown if input is provided + const { + data: bridgingTokenOptions, + error: bridgingTokenOptionsError, + refetch: refetchBridgingTokenOptions, + loading: bridgingTokenOptionsLoading, + } = useBridgingTokensOptions({ input, walletAddress: address, chainFilter }) + // Only call search endpoint if isBalancesOnlySearch is false const { data: searchResultCurrencies, @@ -587,15 +725,29 @@ export function useTokenSectionsForSearchResults( }, [searchResultCurrencies, portfolioBalancesById, searchFilter]) const loading = - portfolioTokenOptionsLoading || portfolioBalancesByIdLoading || (!isBalancesOnlySearch && searchTokensLoading) + portfolioTokenOptionsLoading || + portfolioBalancesByIdLoading || + (!isBalancesOnlySearch && searchTokensLoading) || + bridgingTokenOptionsLoading - const sections = useTokenOptionsSection( + const searchResultsSections = useTokenOptionsSection( TokenOptionSection.SearchResults, // Use local search when only searching balances isBalancesOnlySearch ? portfolioTokenOptions : searchResults, ) + // If there are bridging options, we need to extract them from the search results and then prepend them as a new section above. + // The remaining non-bridging search results will be shown in a section with a different name + const networkName = chainFilter ? UNIVERSE_CHAIN_INFO[chainFilter].label : undefined + const searchResultsSectionHeader = networkName + ? t('tokens.selector.section.otherSearchResults', { network: networkName }) + : undefined + const sections = isBridgingEnabled + ? mergeSearchResultsWithBridgingTokens(searchResultsSections, bridgingTokenOptions, searchResultsSectionHeader) + : searchResultsSections + const error = + (!bridgingTokenOptions && bridgingTokenOptionsError) || (!portfolioBalancesById && portfolioBalancesByIdError) || (!portfolioTokenOptions && portfolioTokenOptionsError) || (!isBalancesOnlySearch && !searchResults && searchTokensError) @@ -604,7 +756,16 @@ export function useTokenSectionsForSearchResults( refetchPortfolioBalances?.() refetchSearchTokens?.() refetchPortfolioTokenOptions?.() - }, [refetchPortfolioBalances, refetchPortfolioTokenOptions, refetchSearchTokens]) + if (isBridgingEnabled) { + refetchBridgingTokenOptions?.() + } + }, [ + isBridgingEnabled, + refetchBridgingTokenOptions, + refetchPortfolioBalances, + refetchPortfolioTokenOptions, + refetchSearchTokens, + ]) return useMemo( () => ({ diff --git a/packages/uniswap/src/components/TokenSelector/suggestedTokensKeyExtractor.tsx b/packages/uniswap/src/components/TokenSelector/suggestedTokensKeyExtractor.tsx deleted file mode 100644 index d1e8fb6273e..00000000000 --- a/packages/uniswap/src/components/TokenSelector/suggestedTokensKeyExtractor.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { TokenOption } from 'uniswap/src/components/TokenSelector/types' - -export function suggestedTokensKeyExtractor(suggestedTokens: TokenOption[]): string { - return suggestedTokens.map((token) => token.currencyInfo.currencyId).join('-') -} diff --git a/packages/uniswap/src/components/TokenSelector/types.ts b/packages/uniswap/src/components/TokenSelector/types.ts index c40e22557d8..701aa99af81 100644 --- a/packages/uniswap/src/components/TokenSelector/types.ts +++ b/packages/uniswap/src/components/TokenSelector/types.ts @@ -1,3 +1,4 @@ +import { TradeableAsset } from 'uniswap/src/entities/assets' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { UniverseChainId } from 'uniswap/src/types/chains' import { FiatNumberType } from 'utilities/src/format/types' @@ -19,17 +20,20 @@ export enum TokenOptionSection { SearchResults = 'searchResults', SuggestedTokens = 'suggestedTokens', BridgingTokens = 'bridgingTokens', + SearchResultsByNetwork = 'searchResultsByNetwork', } export type TokenSection = { data: TokenOption[] | TokenOption[][] sectionKey: TokenOptionSection + name?: string rightElement?: JSX.Element } export type TokenSectionsHookProps = { activeAccountAddress?: string chainFilter: UniverseChainId | null + input?: TradeableAsset isKeyboardOpen?: boolean } diff --git a/packages/uniswap/src/components/TokenSelector/utils.tsx b/packages/uniswap/src/components/TokenSelector/utils.tsx index 002fdc3006b..2061b470727 100644 --- a/packages/uniswap/src/components/TokenSelector/utils.tsx +++ b/packages/uniswap/src/components/TokenSelector/utils.tsx @@ -1,4 +1,7 @@ import { TokenOption, TokenOptionSection, TokenSection } from 'uniswap/src/components/TokenSelector/types' +import { tradingApiSwappableTokenToCurrencyInfo } from 'uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo' +import { SafetyLevel as GqlSafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { GetSwappableTokensResponse, SafetyLevel } from 'uniswap/src/data/tradingApi/__generated__' import { CurrencyInfo, PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { areCurrencyIdsEqual } from 'uniswap/src/utils/currencyId' import { differenceWith } from 'utilities/src/primitives/array' @@ -11,6 +14,37 @@ export function createEmptyBalanceOption(currencyInfo: CurrencyInfo): TokenOptio } } +export function createEmptyTokenOptionFromSwappableToken( + token: GetSwappableTokensResponse['tokens'][0], +): TokenOption | undefined { + const currencyInfo = tradingApiSwappableTokenToCurrencyInfo(token) + + if (!currencyInfo) { + return undefined + } + + return { + currencyInfo, + balanceUSD: null, + quantity: null, + } +} + +export function toGqlSafetyLevel(safetyLevel: SafetyLevel): GqlSafetyLevel | null { + switch (safetyLevel) { + case SafetyLevel.BLOCKED: + return GqlSafetyLevel.Blocked + case SafetyLevel.MEDIUM_WARNING: + return GqlSafetyLevel.MediumWarning + case SafetyLevel.STRONG_WARNING: + return GqlSafetyLevel.StrongWarning + case SafetyLevel.VERIFIED: + return GqlSafetyLevel.Verified + default: + return null + } +} + // get items in `currencies` that are not in `without` // e.g. difference([B, C, D], [A, B, C]) would return ([D]) export function tokenOptionDifference( @@ -33,7 +67,7 @@ export function formatSearchResults( searchFilter: string | null, ): TokenOption[] | undefined { if (!searchResultCurrencies) { - return + return undefined } const formattedOptions = searchResultCurrencies.map((currencyInfo): TokenOption => { @@ -59,6 +93,69 @@ export function formatSearchResults( return formattedOptions } +/** + * Utility to merge the search results with the bridging tokens. + * Also updates the search results section name accordingly + */ +export function mergeSearchResultsWithBridgingTokens( + searchResults: TokenSection[] | undefined, + bridgingTokens: TokenOption[] | undefined, + sectionHeaderString: string | undefined, +): TokenSection[] | undefined { + if (!searchResults || !bridgingTokens || bridgingTokens.length === 0) { + return searchResults + } + + const extractedBridgingTokens: TokenOption[] = [] + + const extractedSearchResults = searchResults.map((section) => { + const sectionResults2D: TokenOption[][] = [] + const sectionResults: TokenOption[] = [] + section.data.forEach((token) => { + if (isTokenOptionArray(token)) { + // 2D array is for horizontal token list sections, which is not applicable for search results + sectionResults2D.push(token) + return + } + + const isBridgingToken = bridgingTokens.some((bridgingToken) => + areCurrencyIdsEqual(token.currencyInfo.currencyId, bridgingToken.currencyInfo.currencyId), + ) + + if (isBridgingToken) { + extractedBridgingTokens.push(token) + } else { + sectionResults.push(token) + } + }) + + return { + ...section, + data: sectionResults2D.length > 0 ? sectionResults2D : sectionResults, + } + }) + + const bridgingSection: TokenSection = { + sectionKey: TokenOptionSection.BridgingTokens, + data: extractedBridgingTokens, + } + + // Update the search results section name to "Other tokens on {{network}}" if there is a valid bridging section + const searchResultsSection = extractedSearchResults.find( + (section) => section.sectionKey === TokenOptionSection.SearchResults, + ) + if (bridgingSection.data.length > 0 && searchResultsSection && sectionHeaderString) { + searchResultsSection.name = sectionHeaderString + } + + // Remove empty sections + return [bridgingSection, ...extractedSearchResults].filter((section) => section.data.length > 0) +} + +export function isTokenOptionArray(option: TokenOption | TokenOption[]): option is TokenOption[] { + return Array.isArray(option) +} + function isExactTokenOptionMatch(searchResult: TokenOption, query: string): boolean { return ( searchResult.currencyInfo.currency.name?.toLowerCase() === query.toLowerCase() || @@ -70,12 +167,25 @@ export function useTokenOptionsSection( sectionKey: TokenOptionSection, tokenOptions?: TokenOption[] | TokenOption[][], rightElement?: JSX.Element, + name?: string, ): TokenSection[] | undefined { - return tokenOptions?.length + if (!tokenOptions) { + return undefined + } + + // If it is a 2D array, check if any of the inner arrays are not empty + // Otherwise, check if the array is not empty + const is2DArray = tokenOptions?.length > 0 && Array.isArray(tokenOptions[0]) + const hasData = is2DArray + ? tokenOptions.some((item) => isTokenOptionArray(item) && item.length > 0) + : tokenOptions.length > 0 + + return hasData ? [ { sectionKey, data: tokenOptions, + name, rightElement, }, ] diff --git a/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx b/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx index 0684ed90156..57f246516f4 100644 --- a/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx +++ b/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx @@ -93,7 +93,7 @@ export function ActionSheetDropdown({ useEffect(() => { if (!isWeb) { - return + return undefined } function resizeListener(): void { @@ -245,7 +245,7 @@ function DropdownContent({ const [windowScrollY, setWindowScrollY] = useState(0) useEffect(() => { if (!isWeb) { - return + return undefined } function scrollListener(): void { diff --git a/packages/uniswap/src/components/modals/Modal.native.tsx b/packages/uniswap/src/components/modals/Modal.native.tsx index 6182121f08b..5a5680c45dd 100644 --- a/packages/uniswap/src/components/modals/Modal.native.tsx +++ b/packages/uniswap/src/components/modals/Modal.native.tsx @@ -39,6 +39,8 @@ function useModalBackHandler(modalRef: React.RefObject, enabled: bool return subscription.remove } + + return undefined }, [modalRef, enabled]) } diff --git a/packages/uniswap/src/components/modals/Modal.web.tsx b/packages/uniswap/src/components/modals/Modal.web.tsx index 7c29afeb2ad..76c85e24b63 100644 --- a/packages/uniswap/src/components/modals/Modal.web.tsx +++ b/packages/uniswap/src/components/modals/Modal.web.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react' -import { AdaptiveWebModal } from 'ui/src' +import { AdaptiveWebModal, isWeb } from 'ui/src' import { ModalProps } from 'uniswap/src/components/modals/ModalProps' import Trace from 'uniswap/src/features/telemetry/Trace' +import { INTERFACE_NAV_HEIGHT } from 'uniswap/src/theme/heights' import { isExtension, isInterface } from 'utilities/src/platform' const ANIMATION_MS = 200 @@ -15,6 +16,7 @@ export function Modal({ isModalOpen = true, alignment = 'center', maxWidth, + maxHeight, padding = '$spacing12', }: ModalProps): JSX.Element { const [fullyClosed, setFullyClosed] = useState(false) @@ -35,9 +37,11 @@ export function Modal({ clearTimeout(tm) } } + return undefined }, [isModalOpen]) const isTopAligned = alignment === 'top' + const justifyContent = isTopAligned ? 'flex-start' : isWeb ? undefined : 'center' return ( @@ -49,9 +53,15 @@ export function Modal({ backgroundColor={backgroundColor} height={fullScreen ? '100%' : undefined} isOpen={isModalOpen} - justifyContent={isTopAligned ? 'flex-start' : 'center'} + justifyContent={justifyContent} m="$none" maxWidth={maxWidth} + maxHeight={maxHeight} + $sm={{ + '$platform-web': { + height: `calc(100dvh - ${INTERFACE_NAV_HEIGHT}px)`, + }, + }} p={padding} position={isTopAligned ? 'absolute' : undefined} top={isTopAligned ? '$spacing16' : undefined} diff --git a/packages/uniswap/src/components/modals/ModalProps.tsx b/packages/uniswap/src/components/modals/ModalProps.tsx index c3bf8a557ca..1280085f004 100644 --- a/packages/uniswap/src/components/modals/ModalProps.tsx +++ b/packages/uniswap/src/components/modals/ModalProps.tsx @@ -33,5 +33,6 @@ export type ModalProps = PropsWithChildren<{ alignment?: 'center' | 'top' hideScrim?: boolean maxWidth?: number + maxHeight?: number padding?: SpaceTokens }> diff --git a/packages/uniswap/src/components/modals/PaginatedModals.tsx b/packages/uniswap/src/components/modals/PaginatedModals.tsx index 19c6b785126..a888d85557e 100644 --- a/packages/uniswap/src/components/modals/PaginatedModals.tsx +++ b/packages/uniswap/src/components/modals/PaginatedModals.tsx @@ -1,7 +1,7 @@ import { memo, useCallback, useRef, useState } from 'react' export type PaginatedModalProps = { - onConfirm: () => void + onAcknowledge: () => void onClose: () => void key: number } @@ -62,5 +62,5 @@ type PageProps = { } const Page = memo(function _Page({ modalIndex, renderModal, onClose, onConfirm }: PageProps): JSX.Element | null { - return renderModal({ onClose: () => onClose(modalIndex), onConfirm, key: modalIndex }) + return renderModal({ onClose: () => onClose(modalIndex), onAcknowledge: onConfirm, key: modalIndex }) }) diff --git a/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx b/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx index e92eba7e834..1c1c58045fb 100644 --- a/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx +++ b/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx @@ -3,7 +3,7 @@ import { PropsWithChildren, ReactNode } from 'react' import type { ColorValue } from 'react-native' import { Button, Flex, Text, useSporeColors } from 'ui/src' import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled' -import { opacify } from 'ui/src/theme' +import { ThemeNames, opacify } from 'ui/src/theme' import { Modal } from 'uniswap/src/components/modals/Modal' import { getAlertColor } from 'uniswap/src/components/modals/WarningModal/getAlertColor' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' @@ -11,21 +11,20 @@ import { ModalNameType } from 'uniswap/src/features/telemetry/constants' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { isWeb } from 'utilities/src/platform' -export type WarningModalProps = { - isOpen: boolean +type WarningModalContentProps = { onClose?: () => void - onCancel?: () => void - onConfirm?: () => void - modalName: ModalNameType + onReject?: () => void + onAcknowledge?: () => void + hideHandlebar?: boolean title?: string titleComponent?: ReactNode caption?: string captionComponent?: ReactNode - closeText?: string - confirmText?: string + rejectText?: string + acknowledgeText?: string + rejectButtonTheme?: ThemeNames + acknowledgeButtonTheme?: ThemeNames severity?: WarningSeverity - isDismissible?: boolean - hideHandlebar?: boolean icon?: ReactNode // when icon is undefined we default it to triangle, this allows us to hide it hideIcon?: boolean @@ -34,31 +33,106 @@ export type WarningModalProps = { maxWidth?: number } -export function WarningModal({ - isOpen, +export type WarningModalProps = { + isOpen: boolean + isDismissible?: boolean + modalName: ModalNameType +} & WarningModalContentProps + +export function WarningModalContent({ onClose, - onCancel, - onConfirm, - modalName, + onReject, + onAcknowledge, title, titleComponent, caption, captionComponent, - closeText, - confirmText, + rejectText: rejectText, + rejectButtonTheme, + acknowledgeText, + acknowledgeButtonTheme, severity = WarningSeverity.Medium, children, - isDismissible = true, - hideHandlebar = false, icon, hideIcon, - backgroundIconColor, maxWidth, -}: PropsWithChildren): JSX.Element { + hideHandlebar = false, + backgroundIconColor, +}: PropsWithChildren): JSX.Element { const colors = useSporeColors() const alertColor = getAlertColor(severity) const alertColorValue = alertColor.text as keyof typeof colors + return ( + + {!hideIcon && ( + + {icon ?? } + + )} + {title && ( + + {title} + + )} + {titleComponent} + {caption && ( + + {caption} + + )} + {captionComponent} + {children} + + {rejectText && ( + + )} + {acknowledgeText && ( + + )} + + + ) +} + +export function WarningModal(props: PropsWithChildren): JSX.Element { + const { hideHandlebar, isDismissible = true, isOpen, maxWidth, modalName, onClose } = props + const colors = useSporeColors() + return ( - - {!hideIcon && ( - - {icon ?? } - - )} - {title && ( - - {title} - - )} - {titleComponent} - {caption && ( - - {caption} - - )} - {captionComponent} - {children} - - {closeText && ( - - )} - {confirmText && ( - - )} - - + ) } diff --git a/packages/uniswap/src/constants/chains.ts b/packages/uniswap/src/constants/chains.ts index a3976d33932..9c1ef44e41b 100644 --- a/packages/uniswap/src/constants/chains.ts +++ b/packages/uniswap/src/constants/chains.ts @@ -22,7 +22,6 @@ import { DAI_ARBITRUM_ONE, DAI_OPTIMISM, DAI_POLYGON, - POL_POLYGON, USDB_BLAST, USDC, USDC_ARBITRUM, @@ -628,7 +627,7 @@ export const UNIVERSE_CHAIN_INFO: Record = { backendChain: { chain: BackendChainId.Polygon as InterfaceGqlChain, backendSupported: true, - nativeTokenBackendAddress: POL_POLYGON.address, + nativeTokenBackendAddress: '0x0000000000000000000000000000000000001010', isSecondaryChain: false, }, blockWaitMsBeforeWarning: 600000, @@ -685,7 +684,7 @@ export const UNIVERSE_CHAIN_INFO: Record = { chain: BackendChainId.Polygon as InterfaceGqlChain, isSecondaryChain: true, backendSupported: true, - nativeTokenBackendAddress: POL_POLYGON.address, + nativeTokenBackendAddress: '0x0000000000000000000000000000000000001010', }, blockPerMainnetEpochForChainId: 1, blockWaitMsBeforeWarning: 600000, diff --git a/packages/uniswap/src/constants/tokens.ts b/packages/uniswap/src/constants/tokens.ts index cc91c23f8e0..59abbf8fb41 100644 --- a/packages/uniswap/src/constants/tokens.ts +++ b/packages/uniswap/src/constants/tokens.ts @@ -119,22 +119,6 @@ export const BUSD_BSC = new Token(UniverseChainId.Bnb, '0xe9e7CEA3DedcA5984780Ba export const DAI_BSC = new Token(UniverseChainId.Bnb, '0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3', 18, 'DAI', 'DAI') -export const MATIC_POLYGON = new Token( - UniverseChainId.Polygon, - '0x0000000000000000000000000000000000001010', - 18, - 'MATIC', - 'Matic', -) - -export const POL_POLYGON = new Token( - UniverseChainId.Polygon, - '0x0000000000000000000000000000000000001010', - 18, - 'POL', - 'POL', -) - export const DAI_POLYGON = new Token( UniverseChainId.Polygon, '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', @@ -570,6 +554,8 @@ export function isCelo(chainId: number): chainId is UniverseChainId.Celo | Unive return chainId === UniverseChainId.CeloAlfajores || chainId === UniverseChainId.Celo } +// Celo has a precompile for its native asset that is fully-compliant with ERC20 interface +// so we can treat it as an ERC20 token. (i.e. $CELO pools are created with its ERC20 precompile) function getCeloNativeCurrency(chainId: number): Token { switch (chainId) { case UniverseChainId.CeloAlfajores: @@ -585,6 +571,8 @@ export function isPolygon(chainId: number): chainId is UniverseChainId.Polygon | return chainId === UniverseChainId.PolygonMumbai || chainId === UniverseChainId.Polygon } +// Polygon also has a precompile, but its precompile is not fully erc20-compatible. +// So we treat Polygon's native asset as NativeCurrency since we can't treat it like an ERC20 token. class PolygonNativeCurrency extends NativeCurrency { equals(other: Currency): boolean { return other.isNative && other.chainId === this.chainId @@ -603,7 +591,7 @@ class PolygonNativeCurrency extends NativeCurrency { if (!isPolygon(chainId)) { throw new Error('Not Polygon') } - super(chainId, 18, 'MATIC', 'Matic') + super(chainId, 18, 'POL', 'Polygon Ecosystem Token') } } diff --git a/packages/uniswap/src/constants/urls.ts b/packages/uniswap/src/constants/urls.ts index 52b50630e86..9254eb58562 100644 --- a/packages/uniswap/src/constants/urls.ts +++ b/packages/uniswap/src/constants/urls.ts @@ -88,6 +88,7 @@ export const uniswapUrls = { swap: '/v1/swap', order: '/v1/order', orders: '/v1/orders', + swaps: '/v1/swaps', swappableTokens: '/v1/swappable_tokens', checkLpApproval: '/v1/check_approval_lp', createLp: '/v1/create_lp_position', diff --git a/packages/uniswap/src/data/apiClients/hooks/useQueryWithImmediateGarbageCollection.ts b/packages/uniswap/src/data/apiClients/hooks/useQueryWithImmediateGarbageCollection.ts index ed8a4cbb9b7..3069320d8f9 100644 --- a/packages/uniswap/src/data/apiClients/hooks/useQueryWithImmediateGarbageCollection.ts +++ b/packages/uniswap/src/data/apiClients/hooks/useQueryWithImmediateGarbageCollection.ts @@ -25,7 +25,7 @@ export function useQueryWithImmediateGarbageCollection( useEffect(() => { if (skip || !immediateGcTime) { - return + return undefined } const timeSinceLastUpdate = Date.now() - dataUpdatedAt diff --git a/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts b/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts index 9a97d265116..3b954208bcc 100644 --- a/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts +++ b/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts @@ -5,12 +5,15 @@ import { SwappableTokensParams } from 'uniswap/src/data/apiClients/tradingApi/us import { ApprovalRequest, ApprovalResponse, + BridgeQuote, + ChainId, ClassicQuote, CreateSwapRequest, CreateSwapResponse, DutchQuoteV2, GetOrdersResponse, GetSwappableTokensResponse, + GetSwapsResponse, IndicativeQuoteRequest, IndicativeQuoteResponse, OrderRequest, @@ -20,11 +23,12 @@ import { ReduceLPPositionRequest, ReduceLPPositionResponse, Routing, + TransactionHash, } from 'uniswap/src/data/tradingApi/__generated__' // TradingAPI team is looking into updating type generation to produce the following types for it's current QuoteResponse type: // See: https://linear.app/uniswap/issue/API-236/explore-changing-the-quote-schema-to-pull-out-a-basequoteresponse -export type DiscriminatedQuoteResponse = ClassicQuoteResponse | DutchQuoteResponse +export type DiscriminatedQuoteResponse = ClassicQuoteResponse | DutchQuoteResponse | BridgeQuoteResponse export type DutchQuoteResponse = QuoteResponse & { quote: DutchQuoteV2 @@ -36,6 +40,11 @@ export type ClassicQuoteResponse = QuoteResponse & { routing: Routing.CLASSIC } +export type BridgeQuoteResponse = QuoteResponse & { + quote: BridgeQuote + routing: Routing.BRIDGE +} + export const TRADING_API_CACHE_KEY = 'TradingApi' const TradingApiClient = createApiClient({ @@ -102,3 +111,12 @@ export async function reduceLpPosition(params: ReduceLPPositionRequest): Promise }), }) } + +export async function fetchSwaps(params: { txHashes: TransactionHash[]; chainId: ChainId }): Promise { + return await TradingApiClient.get(uniswapUrls.tradingApiPaths.swaps, { + params: { + txHashes: params.txHashes.join(','), + chainId: params.chainId, + }, + }) +} diff --git a/packages/uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo.ts b/packages/uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo.ts new file mode 100644 index 00000000000..41c5b86c534 --- /dev/null +++ b/packages/uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo.ts @@ -0,0 +1,41 @@ +import { toGqlSafetyLevel } from 'uniswap/src/components/TokenSelector/utils' +import { getNativeAddress } from 'uniswap/src/constants/addresses' +import { GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__' +import { toSupportedChainId } from 'uniswap/src/features/chains/utils' +import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' +import { buildCurrency } from 'uniswap/src/features/dataApi/utils' +import { NATIVE_ADDRESS_FOR_TRADING_API } from 'uniswap/src/features/transactions/swap/utils/tradingApi' +import { currencyId } from 'uniswap/src/utils/currencyId' + +export function tradingApiSwappableTokenToCurrencyInfo( + token: GetSwappableTokensResponse['tokens'][0], +): CurrencyInfo | undefined { + const isNative = token.address === NATIVE_ADDRESS_FOR_TRADING_API + const supportedChainId = toSupportedChainId(token.chainId) + + if (!supportedChainId) { + return undefined + } + + const currency = buildCurrency({ + chainId: supportedChainId, + address: isNative ? getNativeAddress(supportedChainId) : token.address, + decimals: token.decimals, + symbol: token.symbol, + name: token.name, + }) + + if (!currency) { + return undefined + } + + const currencyInfo: CurrencyInfo = { + currency, + currencyId: currencyId(currency), + logoUrl: token.project?.logo?.url, + isSpam: token.project?.isSpam, + safetyLevel: toGqlSafetyLevel(token.project?.safetyLevel), + } + + return currencyInfo +} diff --git a/packages/uniswap/src/data/apiClients/uniswapApi/useGasFeeQuery.ts b/packages/uniswap/src/data/apiClients/uniswapApi/useGasFeeQuery.ts index 0c6caeabb77..9b5003526a1 100644 --- a/packages/uniswap/src/data/apiClients/uniswapApi/useGasFeeQuery.ts +++ b/packages/uniswap/src/data/apiClients/uniswapApi/useGasFeeQuery.ts @@ -4,7 +4,7 @@ import { uniswapUrls } from 'uniswap/src/constants/urls' import { useQueryWithImmediateGarbageCollection } from 'uniswap/src/data/apiClients/hooks/useQueryWithImmediateGarbageCollection' import { UseQueryWithImmediateGarbageCollectionApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UNISWAP_API_CACHE_KEY, fetchGasFee } from 'uniswap/src/data/apiClients/uniswapApi/UniswapApiClient' -import { GasStrategy } from 'uniswap/src/data/tradingApi/__generated__' +import { GasStrategy } from 'uniswap/src/data/tradingApi/types' import { GasFeeResponse } from 'uniswap/src/features/gas/types' export function useGasFeeQuery({ diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql index ab7402b2f6a..98fc9323a64 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql @@ -369,12 +369,15 @@ query PortfolioBalances( logoUrl name safetyLevel - isSpam } feeData { buyFeeBps sellFeeBps } + protectionInfo { + result + attackTypes + } } tokenProjectMarket { relativeChange24: pricePercentChange(duration: DAY) { @@ -435,9 +438,9 @@ query MultiplePortfolioBalances( address symbol decimals + name project { id - name logoUrl safetyLevel isSpam @@ -644,10 +647,10 @@ query TokenDetailsScreen( query TokenProjects($contracts: [ContractInput!]!) { tokenProjects(contracts: $contracts) { id - name logoUrl safetyLevel tokens { + name chain address decimals @@ -1043,9 +1046,9 @@ query SearchTokens($searchQuery: String!, $chains: [Chain!]) { address decimals symbol + name project { id - name logoUrl safetyLevel } @@ -1071,9 +1074,9 @@ query ExploreSearch( value } } + name project { id - name logoUrl safetyLevel } @@ -1116,9 +1119,9 @@ fragment TopTokenParts on Token { value } } + name project { id - name logoUrl markets(currencies: [USD]) { id @@ -1163,8 +1166,8 @@ fragment AITopTokenParts on Token { value } } + name project { - name markets(currencies: [USD]) { price { value @@ -1194,9 +1197,9 @@ fragment HomeScreenTokenParts on Token { symbol chain address + name project { id - name logoUrl markets(currencies: [USD]) { id @@ -1221,22 +1224,23 @@ query HomeScreenTokens($contracts: [ContractInput!]!, $chain: Chain!) { query FavoriteTokenCard($chain: Chain!, $address: String) { token(chain: $chain, address: $address) { - symbol - chain + id address + chain + symbol + name + market(currency: USD) { + id + price { + value + } + pricePercentChange(duration: DAY) { + value + } + } project { id - name logoUrl - markets(currencies: [USD]) { - id - price { - value - } - pricePercentChange24h { - value - } - } } } } @@ -1246,9 +1250,7 @@ query Tokens($contracts: [ContractInput!]!) { symbol chain address - project { - name - } + name } } diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/ConvertWeb.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/ConvertWeb.graphql deleted file mode 100644 index 87f250ed1ad..00000000000 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/ConvertWeb.graphql +++ /dev/null @@ -1,7 +0,0 @@ -query ConvertWeb($toCurrency: Currency!) { - convert(fromAmount: { currency: USD, value: 1.0 }, toCurrency: $toCurrency) { - id - value - currency - } -} diff --git a/packages/uniswap/src/data/rest/getPools.ts b/packages/uniswap/src/data/rest/getPools.ts new file mode 100644 index 00000000000..afa1211c150 --- /dev/null +++ b/packages/uniswap/src/data/rest/getPools.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-restricted-imports */ +import { PartialMessage } from '@bufbuild/protobuf' +import { ConnectError } from '@connectrpc/connect' +import { useQuery } from '@connectrpc/connect-query' +import { UseQueryResult } from '@tanstack/react-query' +import { listPools } from '@uniswap/client-pools/dist/pools/v1/api-PoolsService_connectquery' +import { ListPoolsRequest, ListPoolsResponse } from '@uniswap/client-pools/dist/pools/v1/api_pb' +import { getPositionsTestTransport } from 'uniswap/src/data/rest/getPositions' + +export function useGetPoolsByTokens( + input?: PartialMessage, +): UseQueryResult { + return useQuery(listPools, input, { transport: getPositionsTestTransport }) +} diff --git a/packages/uniswap/src/data/rest/getPositions.ts b/packages/uniswap/src/data/rest/getPositions.ts index b0c76327587..23fa7a9d6ca 100644 --- a/packages/uniswap/src/data/rest/getPositions.ts +++ b/packages/uniswap/src/data/rest/getPositions.ts @@ -1,115 +1,18 @@ /* eslint-disable no-restricted-imports */ import { PartialMessage } from '@bufbuild/protobuf' import { ConnectError } from '@connectrpc/connect' +import { useQuery } from '@connectrpc/connect-query' +import { createConnectTransport } from '@connectrpc/connect-web' import { UseQueryResult } from '@tanstack/react-query' -import { GetPositionsRequest, GetPositionsResponse } from '@uniswap/client-pools/dist/pools/v1/api_pb' -import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { listPositions } from '@uniswap/client-pools/dist/pools/v1/api-PoolsService_connectquery' +import { ListPositionsRequest, ListPositionsResponse } from '@uniswap/client-pools/dist/pools/v1/api_pb' -const TEST_POSITIONS_DATA = { - positions: [ - { - chainId: 1, - protocolVersion: ProtocolVersion.V2, - v2Pair: { - token0: { - chainId: 1, - address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - symbol: 'USDC', - decimals: 6, - }, - token1: { - chainId: 1, - address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - symbol: 'WETH', - decimals: 18, - }, - liquidityToken: { - chainId: 1, - address: '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc', - symbol: 'UNI-V2', - decimals: 18, - }, - reserve0: '45641156316559', - reserve1: '17196237072419173119561', - }, - status: PositionStatus.IN_RANGE, - }, - { - chainId: 1, - protocolVersion: ProtocolVersion.V3, - v3Position: { - tokenId: '785499', - tickLower: '197440', - tickUpper: '198810', - liquidity: '0', - token0: { - chainId: 1, - address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - symbol: 'USDC', - decimals: 6, - }, - token1: { - chainId: 1, - address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - symbol: 'WETH', - decimals: 18, - }, - feeTier: '500', - currentTick: '197497', - currentPrice: '1539126286317107746121848509365654', - tickSpacing: '10', - token0UncollectedFees: '0', - token1UncollectedFees: '0', - }, - status: PositionStatus.OUT_OF_RANGE, - }, - { - chainId: 1, - protocolVersion: ProtocolVersion.V4, - v4Position: { - poolPosition: { - tokenId: '785426', - tickLower: '197110', - tickUpper: '197730', - liquidity: '45985818120589024', - token0: { - chainId: 1, - address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - symbol: 'USDC', - decimals: 6, - }, - token1: { - chainId: 1, - address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - symbol: 'WETH', - decimals: 18, - }, - feeTier: '500', - currentTick: '197497', - currentPrice: '1539126286317107746121848509365654', - tickSpacing: '10', - token0UncollectedFees: '0', - token1UncollectedFees: '0', - }, - hooks: [ - { - address: '0x4c9AF439b1A6761B8E549D8d226A468a6b2803A8', - }, - ], - }, - status: PositionStatus.IN_RANGE, - }, - ], -} +export const getPositionsTestTransport = createConnectTransport({ + baseUrl: '', // TODO: replace with the prod url and update in csp.json as well +}) export function useGetPositionsQuery( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - input?: PartialMessage, -): UseQueryResult { - return { - data: TEST_POSITIONS_DATA as unknown as GetPositionsResponse, - isLoading: false, - error: null, - } as UseQueryResult - // return useQuery(getPositions, input, { transport: uniswapGetTransport }) + input?: PartialMessage, +): UseQueryResult { + return useQuery(listPositions, input, { transport: getPositionsTestTransport }) } diff --git a/packages/uniswap/src/data/tradingApi/api.json b/packages/uniswap/src/data/tradingApi/api.json index cd1d26b938f..af1272aacab 100644 --- a/packages/uniswap/src/data/tradingApi/api.json +++ b/packages/uniswap/src/data/tradingApi/api.json @@ -1 +1 @@ -{"openapi":"3.0.0","servers":[{"description":"Uniswap trading APIs Beta","url":"https://beta.trade-api.gateway.uniswap.org/v1"},{"description":"Uniswap trading APIs","url":"https://trade-api.gateway.uniswap.org/v1"}],"info":{"version":"1.0.0","title":"Token Trading","description":"Uniswap trading APIs for fungible tokens."},"paths":{"/check_approval":{"post":{"tags":["Approval"],"summary":"Check if token approval is required","description":"Checks if the swapper has the required approval. If the swapper does not have the required approval, then the response will include the transaction to approve the token. If the swapper has the required approval, then the response will be empty. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fee for the approval transaction.","operationId":"check_approval","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ApprovalSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/quote":{"post":{"tags":["Quote"],"summary":"Get a quote","description":"Get a quote according to the provided configuration. Optionally adds a fee to the quote according to the API key being used. The fee is **ALWAYS** taken from the output token. If there is a fee and the trade is `EXACT_INPUT`, then the output amount will **NOT** include the fee subtraction. For `EXACT_INPUT` swaps, use `portionBips` to calculate the fee from the quoted amount. If there is a fee and the trade is `EXACT_OUTPUT`, then the input amount will **NOT** include the fee addition to account for the fee. For `EXACT_OUTPUT` swaps, use `portionAmount` to get the fee. \n \n We also support Wrapping and Unwrapping of native tokens on their respective chains. Wrapping and Unwrapping only works for when `routingPreference` is `CLASSIC`, `BEST_PRICE`, or `BEST_PRICE_V2`. We do not support `UNISWAPX` or `UNISWAPX_V2` for these actions.","operationId":"aggregator_quote","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/QuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/order":{"post":{"tags":["Order"],"summary":"Create a gasless order","description":"Submits a new gasless encoded order. The order will be validated and if valid, will be submitted to the filler network. The network will try to fill the order at the quoted `startAmount`, and if not, the amount will start decaying until the `endAmount` is reached. While the order is within `decayEndTime`, the `orderStatus` is `open`. If the order does not get filled after the `decayEndTime` has passed, that is reflected in the `expired` `orderStatus`. then The order will be filled at the best price possible. Once the order is filled, `orderStatus` becomes `filled`.","operationId":"post_dutch_order","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/OrderSuccess201"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/orders":{"get":{"tags":["Order"],"summary":"Get gasless orders","description":"Retrieve gasless orders filtered by query param(s). Some fields on the order can be used as query param.","operationId":"get_dutch_order","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/orderTypeParam"},{"$ref":"#/components/parameters/orderIdParam"},{"$ref":"#/components/parameters/orderIdsParam"},{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/orderStatusParam"},{"$ref":"#/components/parameters/swapperParam"},{"$ref":"#/components/parameters/sortKeyParam"},{"$ref":"#/components/parameters/sortParam"},{"$ref":"#/components/parameters/fillerParam"},{"$ref":"#/components/parameters/cursorParam"}],"responses":{"200":{"$ref":"#/components/responses/OrdersSuccess200"},"400":{"$ref":"#/components/responses/OrdersBadRequest400"},"404":{"$ref":"#/components/responses/OrdersNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swap":{"post":{"tags":["Swap"],"summary":"Create swap calldata","description":"Create the calldata for a swap transaction (including wrap/unwrap) against the Uniswap Protocols. If the `quote` parameter includes the fee parameters, then the calldata will include the fee disbursement. The gas estimates will be **more precise** when the the response calldata would be valid if submitted on-chain.","operationId":"create_swap_transaction","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapRequest"}}}},"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"responses":{"200":{"$ref":"#/components/responses/CreateSwapSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/SwapUnauthorized401"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swaps":{"get":{"tags":["Swap"],"summary":"Get swaps status","description":"Get the status of a swap or bridge transactions.","operationId":"get_swaps","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/transactionHashParam"},{"$ref":"#/components/parameters/chainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwapsSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/indicative_quote":{"post":{"tags":["IndicativeQuote"],"summary":"Get an indicative quote","description":"Get an indicative quote according to the provided configuration. The quote will not include a fee.","operationId":"indicative_quote","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IndicativeQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/send":{"post":{"tags":["Send"],"summary":"Create send calldata","description":"Create the calldata for a send transaction.","operationId":"create_send","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateSendSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/SendNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swappable_tokens":{"get":{"tags":["SwappableTokens"],"summary":"Get swappable tokens","description":"Get the swappable tokens for the given configuration. Either tokenIn (with tokenInChainId or (tokenInChainId and tokenOutChainId)) or tokenOut (with tokenOutChainId or (tokenOutChainId and tokenInChainId)) must be provided but not both.","operationId":"get_swappable_tokens","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/tokenInParam"},{"$ref":"#/components/parameters/tokenOutParam"},{"$ref":"#/components/parameters/bridgeTokenInChainIdParam"},{"$ref":"#/components/parameters/bridgeTokenOutChainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwappableTokensSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/limit_order_quote":{"post":{"tags":["LimitOrderQuote"],"summary":"Get a limit order quote","description":"Get a quote for a limit order according to the provided configuration.","operationId":"get_limit_order_quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/LimitOrderQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/check_approval_lp":{"post":{"tags":["Liquidity"],"summary":"Check if tokens and permits need to be approved to add liquidity","description":"Checks if the wallet address has the required approvals. If the wallet address does not have the required approval, then the response will include the transactions to approve the tokens. If the wallet address has the required approval, then the response will be empty for the corresponding tokens. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the approval transactions.","operationId":"check_approval_lp","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CheckApprovalLPSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/create_lp_position":{"post":{"tags":["Liquidity"],"summary":"Create pool and position calldata","description":"Create pool and position calldata. If the pool is not yet created, then the response will include the transaction to create the new pool with the initial price. If the pool is already created, then the response will not have the transaction to create the pool. The response will also have the transaction to create the position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the creation transactions.","operationId":"create_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/increase_lp_position":{"post":{"tags":["Liquidity"],"summary":"Increase LP position calldata","description":"The response will also have the transaction to increase the position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the increase transaction.","operationId":"increase_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IncreaseLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/reduce_lp_position":{"post":{"tags":["Liquidity"],"summary":"Reduce LP position calldata","description":"The response will also have the transaction to reduce the position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the reduce transaction.","operationId":"reduce_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReduceLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ReduceLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/claim_lp_fees":{"post":{"tags":["Liquidity"],"summary":"Claim LP fees calldata","description":"The response will also have the transaction to claim the fees for an LP position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the claim transaction.","operationId":"claim_lp_fees","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ClaimLPFeesSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}}},"components":{"responses":{"OrdersSuccess200":{"description":"The request orders matching the query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrdersResponse"}}}},"OrderSuccess201":{"description":"Encoded order submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"QuoteSuccess200":{"description":"Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"LimitOrderQuoteSuccess200":{"description":"Limit Order Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteResponse"}}}},"CheckApprovalLPSuccess200":{"description":"Approve LP successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPResponse"}}}},"ApprovalSuccess200":{"description":"Check approval successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalResponse"}}}},"CreateSendSuccess200":{"description":"Create send successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendResponse"}}}},"CreateSwapSuccess200":{"description":"Create swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapResponse"}}}},"GetSwapsSuccess200":{"description":"Get swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwapsResponse"}}}},"GetSwappableTokensSuccess200":{"description":"Get swappable tokens successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwappableTokensResponse"}}}},"CreateLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionResponse"}}}},"IncreaseLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionResponse"}}}},"ReduceLPPositionSuccess200":{"description":"Reduce LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReduceLPPositionResponse"}}}},"ClaimLPFeesSuccess200":{"description":"Claim LP Fees successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesResponse"}}}},"BadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"ApprovalUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"ApprovalNotFound404":{"description":"ResourceNotFound eg. Token allowance not found or Gas info not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"Unauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"QuoteNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SendNotFound404":{"description":"ResourceNotFound eg. Gas fee not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SwapBadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"SwapUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked or Fee is not enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"SwapNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersNotFound404":{"description":"Orders not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"LPNotFound404":{"description":"ResourceNotFound eg. Cant Find LP Position.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersBadRequest400":{"description":"RequestValidationError eg. Token allowance not valid or Insufficient Funds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"RateLimitedErr429":{"description":"Ratelimited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err429"}}}},"InternalErr500":{"description":"Unexpected error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err500"}}}},"Timeout504":{"description":"Request duration limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err504"}}}},"IndicativeQuoteSuccess200":{"description":"Indicative quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteResponse"}}}}},"schemas":{"NullablePermit":{"allOf":[{"$ref":"#/components/schemas/Permit"},{"type":"object","nullable":true}]},"TokenAmount":{"type":"string"},"SwapStatus":{"type":"string","enum":["PENDING","SUCCESS","NOT_FOUND","FAILED","EXPIRED"]},"FeeType":{"type":"string","enum":["legacy","eip1559"]},"GasEstimateLegacy":{"type":"object","properties":{"gasPrice":{"type":"string"},"gasLimit":{"type":"string"},"type":{"$ref":"#/components/schemas/FeeType","enum":["legacy"]},"strategy":{"$ref":"#/components/schemas/GasStrategy"},"gasFee":{"type":"string"}},"required":["gasPrice","gasLimit","type","strategy","gasFee"]},"GasEstimateEip1559":{"type":"object","properties":{"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasLimit":{"type":"string"},"type":{"$ref":"#/components/schemas/FeeType","enum":["eip1559"]},"strategy":{"$ref":"#/components/schemas/GasStrategy"},"gasFee":{"type":"string"}},"required":["maxFeePerGas","maxPriorityFeePerGas","gasLimit","type","strategy","gasFee"]},"GasEstimate":{"oneOf":[{"$ref":"#/components/schemas/GasEstimateLegacy"},{"$ref":"#/components/schemas/GasEstimateEip1559"}]},"GasStrategy":{"type":"object","properties":{"limitInflationFactor":{"type":"number"},"priceInflationFactor":{"type":"number"},"percentileThresholdFor1559Fee":{"type":"number"},"minPriorityFeeGwei":{"type":"number","nullable":true},"maxPriorityFeeGwei":{"type":"number","nullable":true}},"required":["limitInflationFactor","priceInflationFactor","percentileThresholdFor1559Fee"]},"GetSwapsResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swaps":{"type":"array","items":{"type":"object","properties":{"swapType":{"$ref":"#/components/schemas/Routing"},"status":{"$ref":"#/components/schemas/SwapStatus"}}}}},"required":["requestId","status"]},"GetSwappableTokensResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"tokens":{"type":"array","items":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"name":{"type":"string"},"symbol":{"type":"string"},"project":{"$ref":"#/components/schemas/TokenProject"},"isSpam":{"type":"boolean"},"decimals":{"type":"number"}},"required":["address","chainId","name","symbol","project","decimals"]}}},"required":["requestId","tokens"]},"CreateSwapRequest":{"type":"object","description":"The parameters **signature** and **permitData** should only be included if *permitData* was returned from **/quote**.","properties":{"quote":{"oneOf":[{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/BridgeQuote"}]},"signature":{"type":"string","description":"The signed permit."},"includeGasInfo":{"type":"boolean","default":false,"deprecated":true,"description":"Use `refreshGasPrice` instead."},"refreshGasPrice":{"type":"boolean","default":false,"description":"If true, the gas price will be re-fetched from the network."},"simulateTransaction":{"type":"boolean","default":false,"description":"If true, the transaction will be simulated. If the simulation results on an onchain error, endpoint will return an error."},"permitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"safetyMode":{"$ref":"#/components/schemas/SwapSafetyMode"},"deadline":{"type":"integer","description":"The deadline for the swap in unix timestamp format. If the deadline is not defined OR in the past then the default deadline is 30 minutes."},"urgency":{"$ref":"#/components/schemas/Urgency"},"gasStrategies":{"type":"array","items":{"$ref":"#/components/schemas/GasStrategy"}}},"required":["quote"]},"CreateSendRequest":{"type":"object","properties":{"sender":{"$ref":"#/components/schemas/Address"},"recipient":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"},"gasStrategies":{"type":"array","items":{"$ref":"#/components/schemas/GasStrategy"}}},"required":["sender","recipient","token","amount"]},"UniversalRouterVersion":{"type":"string","enum":["1.2","2.0"],"default":"1.2"},"Address":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{40}$"},"PositionConfig":{"type":"object","properties":{"poolKey":{"$ref":"#/components/schemas/RequestId"},"tickLower":{"type":"number"},"tickUpper":{"type":"number"}},"required":["poolKey","tickLower","tickUpper"]},"PoolKey":{"type":"object","properties":{"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"fee":{"type":"number"},"tickSpacing":{"type":"number"},"hooks":{"$ref":"#/components/schemas/Address"}},"required":["token0","token1","fee","tickSpacing"]},"ClassicGasUseEstimateUSD":{"description":"The gas fee you would pay if you opted for a CLASSIC swap over a Uniswap X order in terms of USD.","type":"string"},"CreateSwapResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swap":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"gasEstimates":{"type":"array","items":{"$ref":"#/components/schemas/GasEstimate"}}},"required":["requestId","swap"]},"CreateSendResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"send":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"gasFeeUSD":{"type":"number"},"gasEstimates":{"type":"array","items":{"$ref":"#/components/schemas/GasEstimate"}}},"required":["requestId","send"]},"QuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/Quote"},"routing":{"$ref":"#/components/schemas/Routing"},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"LimitOrderQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/DutchQuote"},"routing":{"type":"string","enum":["LIMIT_ORDER"]},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"QuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"swapper":{"$ref":"#/components/schemas/Address"},"slippageTolerance":{"description":"For **Classic** swaps, the slippage tolerance is the maximum amount the price can change between the time the transaction is submitted and the time it is executed. The slippage tolerance is represented as a percentage of the total value of the swap. \n\n Slippage tolerance works differently in **DutchLimit** swaps, it does not set a limit on the Spread in an order. See [here](https://uniswap-docs.readme.io/reference/faqs#why-do-the-uniswapx-quotes-have-more-slippage-than-the-tolerance-i-set) for more information. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","type":"number"},"autoSlippage":{"$ref":"#/components/schemas/AutoSlippage"},"routingPreference":{"$ref":"#/components/schemas/RoutingPreference"},"protocols":{"$ref":"#/components/schemas/Protocols"},"spreadOptimization":{"$ref":"#/components/schemas/SpreadOptimization"},"urgency":{"$ref":"#/components/schemas/Urgency"},"gasStrategies":{"type":"array","items":{"$ref":"#/components/schemas/GasStrategy"}}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut","swapper"]},"LimitOrderQuoteRequest":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"limitPrice":{"type":"string"},"amount":{"type":"string"},"orderDeadline":{"type":"number"},"type":{"$ref":"#/components/schemas/TradeType"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["swapper","type","amount","tokenIn","tokenOut","tokenInChainId","tokenOutChainId"]},"GetOrdersResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orders":{"type":"array","items":{"$ref":"#/components/schemas/UniswapXOrder"}},"cursor":{"type":"string"}},"required":["orders","requestId"]},"OrderResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orderId":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"}},"required":["requestId","orderId","orderStatus"]},"OrderRequest":{"type":"object","properties":{"signature":{"type":"string","description":"The signed permit."},"quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"}]},"routing":{"$ref":"#/components/schemas/Routing"}},"required":["signature","quote"]},"Urgency":{"type":"string","enum":["normal","fast","urgent"],"description":"The urgency determines the urgency of the transaction. The default value is `urgent`.","default":"urgent"},"Protocols":{"type":"array","items":{"$ref":"#/components/schemas/ProtocolItems"},"description":"The protocols to use for the swap/order. If the `protocols` field is defined, then you can only set the `routingPreference` to `BEST_PRICE`"},"Err400":{"type":"object","properties":{"errorCode":{"default":"RequestValidationError","type":"string"},"detail":{"type":"string"}}},"Err401":{"type":"object","properties":{"errorCode":{"default":"UnauthorizedError","type":"string"},"detail":{"type":"string"}}},"Err404":{"type":"object","properties":{"errorCode":{"default":"ResourceNotFound","type":"string"},"detail":{"type":"string"}}},"Err429":{"type":"object","properties":{"errorCode":{"default":"Ratelimited","type":"string"},"detail":{"type":"string"}}},"Err500":{"type":"object","properties":{"errorCode":{"default":"InternalServerError","type":"string"},"detail":{"type":"string"}}},"Err504":{"type":"object","properties":{"errorCode":{"default":"Timeout","type":"string"},"detail":{"type":"string"}}},"ChainId":{"type":"number","enum":[1,10,56,137,8453,42161,81457,43114,42220,7777777,324]},"OrderInput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"}},"required":["token"]},"OrderOutput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"},"isFeeOutput":{"type":"boolean"},"recipient":{"type":"string"}},"required":["token"]},"CosignerData":{"type":"object","properties":{"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"inputOverride":{"type":"string"},"outputOverrides":{"type":"array","items":{"type":"string"}}}},"SettledAmount":{"type":"object","properties":{"tokenOut":{"$ref":"#/components/schemas/Address"},"amountOut":{"type":"string"},"tokenIn":{"$ref":"#/components/schemas/Address"},"amountIn":{"type":"string"}}},"OrderType":{"type":"string","enum":["DutchLimit","Dutch","Dutch_V2"]},"OrderTypeQuery":{"type":"string","enum":["Dutch","Dutch_V2","Dutch_V1_V2","Limit"]},"UniswapXOrder":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/OrderType"},"encodedOrder":{"type":"string"},"signature":{"type":"string"},"nonce":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"},"orderId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"quoteId":{"type":"string"},"swapper":{"type":"string"},"txHash":{"type":"string"},"input":{"$ref":"#/components/schemas/OrderInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/OrderOutput"}},"settledAmounts":{"type":"array","items":{"$ref":"#/components/schemas/SettledAmount"}},"cosignature":{"type":"string"},"cosignerData":{"$ref":"#/components/schemas/CosignerData"}},"required":["encodedOrder","signature","nonce","orderId","orderStatus","chainId","type"]},"SortKey":{"type":"string","enum":["createdAt"]},"OrderId":{"type":"string"},"OrderIds":{"type":"string"},"OrderStatus":{"type":"string","enum":["open","expired","error","cancelled","filled","unverified","insufficient-funds"]},"Permit":{"type":"object","properties":{"domain":{"type":"object"},"values":{"type":"object"},"types":{"type":"object"}}},"TokenProject":{"type":"object","properties":{"logo":{"$ref":"#/components/schemas/TokenProjectLogo"},"safetyLevel":{"$ref":"#/components/schemas/SafetyLevel"},"isSpam":{"type":"boolean"}},"required":["logo","safetyLevel","isSpam"]},"TokenProjectLogo":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DutchInput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"}},"required":["startAmount","endAmount","type"]},"DutchOutput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"},"recipient":{"type":"string"}},"required":["startAmount","endAmount","token","recipient"]},"DutchOrderInfo":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"exclusivityOverrideBps":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchOrderInfoV2":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}},"cosigner":{"$ref":"#/components/schemas/Address"}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchQuote":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfo"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"DutchQuoteV2":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfoV2"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"deadlineBufferSecs":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"BridgeQuote":{"type":"object","properties":{"quoteId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"destinationChainId":{"$ref":"#/components/schemas/ChainId"},"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"quoteTimestamp":{"type":"number"},"gasPrice":{"type":"string"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasFee":{"type":"string"},"gasUseEstimate":{"type":"string"},"gasFeeUSD":{"type":"string"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"}}},"SafetyLevel":{"type":"string","enum":["BLOCKED","MEDIUM_WARNING","STRONG_WARNING","VERIFIED"]},"TradeType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT"]},"Routing":{"type":"string","enum":["DUTCH_LIMIT","CLASSIC","DUTCH_V2","BRIDGE","LIMIT_ORDER"]},"Quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"},{"$ref":"#/components/schemas/BridgeQuote"}]},"CheckApprovalLPRequest":{"type":"object","properties":{"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"includeGasInfo":{"type":"boolean"}}},"CheckApprovalLPResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"token0Approval":{"$ref":"#/components/schemas/TransactionRequest"},"token1Approval":{"$ref":"#/components/schemas/TransactionRequest"},"batchedPermit2Data":{"$ref":"#/components/schemas/NullablePermit"},"gasFee0":{"type":"string"},"gasFee1":{"type":"string"}}},"ApprovalRequest":{"type":"object","properties":{"walletAddress":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"},"gasStrategies":{"type":"array","items":{"$ref":"#/components/schemas/GasStrategy"}},"includeGasInfo":{"type":"boolean","default":false},"tokenOut":{"$ref":"#/components/schemas/Address"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["walletAddress","token","amount"]},"ApprovalResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"approval":{"$ref":"#/components/schemas/TransactionRequest"},"cancel":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"cancelGasFee":{"type":"string"},"gasEstimates":{"type":"array","items":{"$ref":"#/components/schemas/GasEstimate"}}},"required":["requestId","approval","cancel"]},"ClassicQuote":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"swapper":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"slippage":{"type":"number"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei. It does NOT include the additional gas for token approvals."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD. It does NOT include the additional gas for token approvals."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency. It does NOT include the additional gas for token approvals."},"gasEstimates":{"type":"array","items":{"$ref":"#/components/schemas/GasEstimate"}},"route":{"type":"array","items":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/V3PoolInRoute"},{"$ref":"#/components/schemas/V2PoolInRoute"}]}}},"portionBips":{"type":"number","description":"The portion of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionAmount":{"type":"string","description":"The amount of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionRecipient":{"$ref":"#/components/schemas/Address"},"routeString":{"type":"string","description":"The route in string format."},"quoteId":{"type":"string","description":"The quote id. Used for analytics purposes."},"gasUseEstimate":{"type":"string","description":"The estimated gas use. It does NOT include the additional gas for token approvals."},"blockNumber":{"type":"string","description":"The current block number."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."},"txFailureReasons":{"type":"array","items":{"$ref":"#/components/schemas/TransactionFailureReason"}},"priceImpact":{"type":"number","description":"The impact the trade has on the market price of the pool, between 0-100 percent"}}},"WrapUnwrapQuote":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"chainId":{"$ref":"#/components/schemas/ChainId"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency."},"gasUseEstimate":{"type":"string","description":"The estimated gas use."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."}}},"TokenInRoute":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"symbol":{"type":"string"},"decimals":{"type":"string"},"buyFeeBps":{"type":"string"},"sellFeeBps":{"type":"string"}}},"V2Reserve":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/TokenInRoute"},"quotient":{"type":"string"}}},"V2PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v2-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"reserve0":{"$ref":"#/components/schemas/V2Reserve"},"reserve1":{"$ref":"#/components/schemas/V2Reserve"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"V3PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v3-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"sqrtRatioX96":{"type":"string"},"liquidity":{"type":"string"},"tickCurrent":{"type":"string"},"fee":{"type":"string"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"TransactionHash":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{64}$"},"ClassicInput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"}}},"ClassicOutput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"},"recipient":{"$ref":"#/components/schemas/Address"}}},"RequestId":{"type":"string"},"SpreadOptimization":{"type":"string","enum":["EXECUTION","PRICE"],"description":"For **Dutch Limit** orders only. When set to `EXECUTION`, quotes optimize for looser spreads at higher fill rates. When set to `PRICE`, quotes optimize for tighter spreads at lower fill rates","default":"EXECUTION"},"AutoSlippage":{"type":"string","enum":["DEFAULT"],"description":"For **Classic** swaps only. The auto slippage strategy to employ. If auto slippage is not defined then we don't compute it. If the auto slippage strategy is `DEFAULT`, then the swap will use the default slippage tolerance computation. You cannot define auto slippage and slippage tolerance at the same time. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","default":"undefined"},"RoutingPreference":{"type":"string","description":"The routing preference determines which protocol to use for the swap. If the routing preference is `UNISWAPX`, then the swap will be routed through the UniswapX Dutch Auction Protocol. If the routing preference is `CLASSIC`, then the swap will be routed through the Classic Protocol. If the routing preference is `BEST_PRICE`, then the swap will be routed through the protocol that provides the best price. When `UNIXWAPX_V2` is passed, the swap will be routed through the UniswapX V2 Dutch Auction Protocol. When `V3_ONLY` is passed, the swap will be routed ONLY through the Uniswap V3 Protocol. When `V2_ONLY` is passed, the swap will be routed ONLY through the Uniswap V2 Protocol.","enum":["CLASSIC","UNISWAPX","BEST_PRICE","BEST_PRICE_V2","UNISWAPX_V2","V3_ONLY","V2_ONLY"],"default":"BEST_PRICE"},"ProtocolItems":{"type":"string","enum":["V2","V3","V4","UNISWAPX","UNISWAPX_V2"]},"TransactionRequest":{"type":"object","properties":{"to":{"$ref":"#/components/schemas/Address"},"from":{"$ref":"#/components/schemas/Address"},"data":{"type":"string","description":"The calldata for the transaction."},"value":{"type":"string","description":"The value of the transaction in terms of wei in hex format."},"gasLimit":{"type":"string"},"chainId":{"type":"integer"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasPrice":{"type":"string"}},"required":["to","from","data","value","chainId"]},"TransactionFailureReason":{"type":"string","enum":["SIMULATION_ERROR","UNSUPPORTED_SIMULATION"]},"SwapSafetyMode":{"type":"string","enum":["SAFE"],"description":"The safety mode determines the safety level of the swap. If the safety mode is `SAFE`, then the swap will include a SWEEP for the native token."},"IndicativeQuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut"]},"IndicativeQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"input":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"output":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"type":{"$ref":"#/components/schemas/TradeType"}},"required":["requestId","input","output","type"]},"CreateLPPositionRequest":{"type":"object","properties":{"positionConfig":{"$ref":"#/components/schemas/PositionConfig"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"amount0Max":{"type":"string"},"amount1Max":{"type":"string"},"initialPrice":{"type":"string"},"signedBatchedPermit2Data":{"type":"string"},"includeGasInfo":{"type":"boolean"}}},"CreateLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"create":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IncreaseLPPositionRequest":{"type":"object","properties":{"positionConfigId":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"amount0Max":{"type":"string"},"amount1Max":{"type":"string"},"includeGasInfo":{"type":"boolean"}}},"IncreaseLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"increase":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"ReduceLPPositionRequest":{"type":"object","properties":{"positionConfigId":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"amount0Max":{"type":"string"},"amount1Max":{"type":"string"},"collectAsWeth":{"type":"boolean"},"includeGasInfo":{"type":"boolean"}}},"ReduceLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"reduce":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"ClaimLPFeesRequest":{"type":"object","properties":{"positionConfigId":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"collectAsWeth":{"type":"boolean"},"includeGasInfo":{"type":"boolean"}}},"ClaimLPFeesResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"claim":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IndicativeQuoteToken":{"type":"object","properties":{"amount":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/Address"}}}},"parameters":{"universalRouterVersionHeader":{"name":"x-universal-router-version","in":"header","description":"The version of the Universal Router to use for the swap journey. *MUST* be consistent throughout the API calls.","required":false,"schema":{"$ref":"#/components/schemas/UniversalRouterVersion"}},"addressParam":{"name":"address","in":"path","schema":{"$ref":"#/components/schemas/Address"},"required":true},"tokenIdParam":{"name":"tokenId","in":"path","schema":{"type":"string"},"required":true},"cursorParam":{"name":"cursor","in":"query","schema":{"type":"string"},"required":false},"limitParam":{"name":"limit","in":"query","schema":{"type":"number"},"required":false},"chainIdParam":{"name":"chainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenInChainIdParam":{"name":"tokenInChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenOutChainIdParam":{"name":"tokenOutChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"tokenInParam":{"name":"tokenIn","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"tokenOutParam":{"name":"tokenOut","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"addressPathParam":{"name":"address","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"orderStatusParam":{"name":"orderStatus","in":"query","description":"Filter by order status.","required":false,"schema":{"$ref":"#/components/schemas/OrderStatus"}},"orderTypeParam":{"name":"orderType","in":"query","description":"The default orderType is Dutch_V1_V2 and will grab both Dutch and Dutch_V2 orders.","required":false,"schema":{"$ref":"#/components/schemas/OrderTypeQuery"}},"orderIdParam":{"name":"orderId","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrderId"}},"orderIdsParam":{"name":"orderIds","in":"query","required":false,"description":"ids split by commas","schema":{"$ref":"#/components/schemas/OrderIds"}},"swapperParam":{"name":"swapper","in":"query","description":"Filter by swapper address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"fillerParam":{"name":"filler","in":"query","description":"Filter by filler address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"sortKeyParam":{"name":"sortKey","in":"query","description":"Order the query results by the sort key.","required":false,"schema":{"$ref":"#/components/schemas/SortKey"}},"sortParam":{"name":"sort","in":"query","description":"Sort query. For example: `sort=gt(UNIX_TIMESTAMP)`, `sort=between(1675872827, 1675872930)`, or `lt(1675872930)`.","required":false,"schema":{"type":"string"}},"descParam":{"description":"Sort query results by sortKey in descending order.","name":"desc","in":"query","required":false,"schema":{"type":"string"}},"transactionHashParam":{"description":"The transaction hash.","name":"txHash","in":"query","required":true,"schema":{"$ref":"#/components/schemas/TransactionHash"}}},"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"security":[{"apiKey":[]}]} \ No newline at end of file +{"openapi":"3.0.0","servers":[{"description":"Uniswap trading APIs Beta","url":"https://beta.trade-api.gateway.uniswap.org/v1"},{"description":"Uniswap trading APIs","url":"https://trade-api.gateway.uniswap.org/v1"}],"info":{"version":"1.0.0","title":"Token Trading","description":"Uniswap trading APIs for fungible tokens."},"paths":{"/check_approval":{"post":{"tags":["Approval"],"summary":"Check if token approval is required","description":"Checks if the swapper has the required approval. If the swapper does not have the required approval, then the response will include the transaction to approve the token. If the swapper has the required approval, then the response will be empty. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fee for the approval transaction.","operationId":"check_approval","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ApprovalSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/quote":{"post":{"tags":["Quote"],"summary":"Get a quote","description":"Get a quote according to the provided configuration. Optionally adds a fee to the quote according to the API key being used. The fee is **ALWAYS** taken from the output token. If there is a fee and the trade is `EXACT_INPUT`, then the output amount will **NOT** include the fee subtraction. For `EXACT_INPUT` swaps, use `portionBips` to calculate the fee from the quoted amount. If there is a fee and the trade is `EXACT_OUTPUT`, then the input amount will **NOT** include the fee addition to account for the fee. For `EXACT_OUTPUT` swaps, use `portionAmount` to get the fee. \n \n We also support Wrapping and Unwrapping of native tokens on their respective chains. Wrapping and Unwrapping only works for when `routingPreference` is `CLASSIC`, `BEST_PRICE`, or `BEST_PRICE_V2`. We do not support `UNISWAPX` or `UNISWAPX_V2` for these actions.","operationId":"aggregator_quote","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/QuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/order":{"post":{"tags":["Order"],"summary":"Create a gasless order","description":"Submits a new gasless encoded order. The order will be validated and if valid, will be submitted to the filler network. The network will try to fill the order at the quoted `startAmount`, and if not, the amount will start decaying until the `endAmount` is reached. While the order is within `decayEndTime`, the `orderStatus` is `open`. If the order does not get filled after the `decayEndTime` has passed, that is reflected in the `expired` `orderStatus`. then The order will be filled at the best price possible. Once the order is filled, `orderStatus` becomes `filled`.","operationId":"post_dutch_order","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/OrderSuccess201"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/orders":{"get":{"tags":["Order"],"summary":"Get gasless orders","description":"Retrieve gasless orders filtered by query param(s). Some fields on the order can be used as query param.","operationId":"get_dutch_order","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/orderTypeParam"},{"$ref":"#/components/parameters/orderIdParam"},{"$ref":"#/components/parameters/orderIdsParam"},{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/orderStatusParam"},{"$ref":"#/components/parameters/swapperParam"},{"$ref":"#/components/parameters/sortKeyParam"},{"$ref":"#/components/parameters/sortParam"},{"$ref":"#/components/parameters/fillerParam"},{"$ref":"#/components/parameters/cursorParam"}],"responses":{"200":{"$ref":"#/components/responses/OrdersSuccess200"},"400":{"$ref":"#/components/responses/OrdersBadRequest400"},"404":{"$ref":"#/components/responses/OrdersNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swap":{"post":{"tags":["Swap"],"summary":"Create swap calldata","description":"Create the calldata for a swap transaction (including wrap/unwrap) against the Uniswap Protocols. If the `quote` parameter includes the fee parameters, then the calldata will include the fee disbursement. The gas estimates will be **more precise** when the the response calldata would be valid if submitted on-chain.","operationId":"create_swap_transaction","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapRequest"}}}},"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"responses":{"200":{"$ref":"#/components/responses/CreateSwapSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/SwapUnauthorized401"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swaps":{"get":{"tags":["Swap"],"summary":"Get swaps status","description":"Get the status of a swap or bridge transactions.","operationId":"get_swaps","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/transactionHashesParam"},{"$ref":"#/components/parameters/chainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwapsSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/indicative_quote":{"post":{"tags":["IndicativeQuote"],"summary":"Get an indicative quote","description":"Get an indicative quote according to the provided configuration. The quote will not include a fee.","operationId":"indicative_quote","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IndicativeQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/send":{"post":{"tags":["Send"],"summary":"Create send calldata","description":"Create the calldata for a send transaction.","operationId":"create_send","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateSendSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/SendNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swappable_tokens":{"get":{"tags":["SwappableTokens"],"summary":"Get swappable tokens","description":"Get the swappable tokens for the given configuration. Either tokenIn (with tokenInChainId or (tokenInChainId and tokenOutChainId)) or tokenOut (with tokenOutChainId or (tokenOutChainId and tokenInChainId)) must be provided but not both.","operationId":"get_swappable_tokens","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/tokenInParam"},{"$ref":"#/components/parameters/tokenOutParam"},{"$ref":"#/components/parameters/bridgeTokenInChainIdParam"},{"$ref":"#/components/parameters/bridgeTokenOutChainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwappableTokensSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/limit_order_quote":{"post":{"tags":["LimitOrderQuote"],"summary":"Get a limit order quote","description":"Get a quote for a limit order according to the provided configuration.","operationId":"get_limit_order_quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/LimitOrderQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/approve":{"post":{"tags":["Liquidity"],"summary":"Check if tokens and permits need to be approved to add liquidity","description":"Checks if the wallet address has the required approvals. If the wallet address does not have the required approval, then the response will include the transactions to approve the tokens. If the wallet address has the required approval, then the response will be empty for the corresponding tokens. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the approval transactions.","operationId":"check_approval_lp","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CheckApprovalLPSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/create":{"post":{"tags":["Liquidity"],"summary":"Create pool and position calldata","description":"Create pool and position calldata. If the pool is not yet created, then the response will include the transaction to create the new pool with the initial price. If the pool is already created, then the response will not have the transaction to create the pool. The response will also have the transaction to create the position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the creation transactions.","operationId":"create_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/increase":{"post":{"tags":["Liquidity"],"summary":"Increase LP position calldata","description":"The response will also have the transaction to increase the position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the increase transaction.","operationId":"increase_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IncreaseLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/decrease":{"post":{"tags":["Liquidity"],"summary":"Reduce LP position calldata","description":"The response will also have the transaction to reduce the position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the reduce transaction.","operationId":"reduce_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReduceLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ReduceLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/claim":{"post":{"tags":["Liquidity"],"summary":"Claim LP fees calldata","description":"The response will also have the transaction to claim the fees for an LP position for the corresponding pool. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fees for the claim transaction.","operationId":"claim_lp_fees","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ClaimLPFeesSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}}},"components":{"responses":{"OrdersSuccess200":{"description":"The request orders matching the query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrdersResponse"}}}},"OrderSuccess201":{"description":"Encoded order submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"QuoteSuccess200":{"description":"Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"LimitOrderQuoteSuccess200":{"description":"Limit Order Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteResponse"}}}},"CheckApprovalLPSuccess200":{"description":"Approve LP successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPResponse"}}}},"ApprovalSuccess200":{"description":"Check approval successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalResponse"}}}},"CreateSendSuccess200":{"description":"Create send successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendResponse"}}}},"CreateSwapSuccess200":{"description":"Create swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapResponse"}}}},"GetSwapsSuccess200":{"description":"Get swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwapsResponse"}}}},"GetSwappableTokensSuccess200":{"description":"Get swappable tokens successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwappableTokensResponse"}}}},"CreateLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionResponse"}}}},"IncreaseLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionResponse"}}}},"ReduceLPPositionSuccess200":{"description":"Reduce LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReduceLPPositionResponse"}}}},"ClaimLPFeesSuccess200":{"description":"Claim LP Fees successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesResponse"}}}},"BadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"ApprovalUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"ApprovalNotFound404":{"description":"ResourceNotFound eg. Token allowance not found or Gas info not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"Unauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"QuoteNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SendNotFound404":{"description":"ResourceNotFound eg. Gas fee not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SwapBadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"SwapUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked or Fee is not enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"SwapNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersNotFound404":{"description":"Orders not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"LPNotFound404":{"description":"ResourceNotFound eg. Cant Find LP Position.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersBadRequest400":{"description":"RequestValidationError eg. Token allowance not valid or Insufficient Funds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"RateLimitedErr429":{"description":"Ratelimited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err429"}}}},"InternalErr500":{"description":"Unexpected error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err500"}}}},"Timeout504":{"description":"Request duration limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err504"}}}},"IndicativeQuoteSuccess200":{"description":"Indicative quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteResponse"}}}}},"schemas":{"NullablePermit":{"allOf":[{"$ref":"#/components/schemas/Permit"},{"type":"object","nullable":true}]},"TokenAmount":{"type":"string"},"SwapStatus":{"type":"string","enum":["PENDING","SUCCESS","NOT_FOUND","FAILED","EXPIRED"]},"GetSwapsResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swaps":{"type":"array","items":{"type":"object","properties":{"swapType":{"$ref":"#/components/schemas/Routing"},"status":{"$ref":"#/components/schemas/SwapStatus"},"txHash":{"type":"string"},"swapId":{"type":"number"}}}}},"required":["requestId","status"]},"GetSwappableTokensResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"tokens":{"type":"array","items":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"name":{"type":"string"},"symbol":{"type":"string"},"project":{"$ref":"#/components/schemas/TokenProject"},"isSpam":{"type":"boolean"},"decimals":{"type":"number"}},"required":["address","chainId","name","symbol","project","decimals"]}}},"required":["requestId","tokens"]},"CreateSwapRequest":{"type":"object","description":"The parameters **signature** and **permitData** should only be included if *permitData* was returned from **/quote**.","properties":{"quote":{"oneOf":[{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/BridgeQuote"}]},"signature":{"type":"string","description":"The signed permit."},"includeGasInfo":{"type":"boolean","default":false,"deprecated":true,"description":"Use `refreshGasPrice` instead."},"refreshGasPrice":{"type":"boolean","default":false,"description":"If true, the gas price will be re-fetched from the network."},"simulateTransaction":{"type":"boolean","default":false,"description":"If true, the transaction will be simulated. If the simulation results on an onchain error, endpoint will return an error."},"permitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"safetyMode":{"$ref":"#/components/schemas/SwapSafetyMode"},"deadline":{"type":"integer","description":"The deadline for the swap in unix timestamp format. If the deadline is not defined OR in the past then the default deadline is 30 minutes."},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["quote"]},"CreateSendRequest":{"type":"object","properties":{"sender":{"$ref":"#/components/schemas/Address"},"recipient":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["sender","recipient","token","amount"]},"UniversalRouterVersion":{"type":"string","enum":["1.2","2.0"],"default":"1.2"},"Address":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{40}$"},"PositionConfig":{"type":"object","properties":{"poolKey":{"$ref":"#/components/schemas/RequestId"},"tickLower":{"type":"number"},"tickUpper":{"type":"number"}},"required":["poolKey","tickLower","tickUpper"]},"PoolKey":{"type":"object","properties":{"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"fee":{"type":"number"},"tickSpacing":{"type":"number"},"hooks":{"$ref":"#/components/schemas/Address"}},"required":["token0","token1","fee","tickSpacing"]},"ClassicGasUseEstimateUSD":{"description":"The gas fee you would pay if you opted for a CLASSIC swap over a Uniswap X order in terms of USD.","type":"string"},"CreateSwapResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swap":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}},"required":["requestId","swap"]},"CreateSendResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"send":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"gasFeeUSD":{"type":"number"}},"required":["requestId","send"]},"QuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/Quote"},"routing":{"$ref":"#/components/schemas/Routing"},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"LimitOrderQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/DutchQuote"},"routing":{"type":"string","enum":["LIMIT_ORDER"]},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"QuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"swapper":{"$ref":"#/components/schemas/Address"},"slippageTolerance":{"description":"For **Classic** swaps, the slippage tolerance is the maximum amount the price can change between the time the transaction is submitted and the time it is executed. The slippage tolerance is represented as a percentage of the total value of the swap. \n\n Slippage tolerance works differently in **DutchLimit** swaps, it does not set a limit on the Spread in an order. See [here](https://uniswap-docs.readme.io/reference/faqs#why-do-the-uniswapx-quotes-have-more-slippage-than-the-tolerance-i-set) for more information. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","type":"number"},"autoSlippage":{"$ref":"#/components/schemas/AutoSlippage"},"routingPreference":{"$ref":"#/components/schemas/RoutingPreference"},"protocols":{"$ref":"#/components/schemas/Protocols"},"spreadOptimization":{"$ref":"#/components/schemas/SpreadOptimization"},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut","swapper"]},"LimitOrderQuoteRequest":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"limitPrice":{"type":"string"},"amount":{"type":"string"},"orderDeadline":{"type":"number"},"type":{"$ref":"#/components/schemas/TradeType"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["swapper","type","amount","tokenIn","tokenOut","tokenInChainId","tokenOutChainId"]},"GetOrdersResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orders":{"type":"array","items":{"$ref":"#/components/schemas/UniswapXOrder"}},"cursor":{"type":"string"}},"required":["orders","requestId"]},"OrderResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orderId":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"}},"required":["requestId","orderId","orderStatus"]},"OrderRequest":{"type":"object","properties":{"signature":{"type":"string","description":"The signed permit."},"quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"}]},"routing":{"$ref":"#/components/schemas/Routing"}},"required":["signature","quote"]},"Urgency":{"type":"string","enum":["normal","fast","urgent"],"description":"The urgency determines the urgency of the transaction. The default value is `urgent`.","default":"urgent"},"Protocols":{"type":"array","items":{"$ref":"#/components/schemas/ProtocolItems"},"description":"The protocols to use for the swap/order. If the `protocols` field is defined, then you can only set the `routingPreference` to `BEST_PRICE`"},"Err400":{"type":"object","properties":{"errorCode":{"default":"RequestValidationError","type":"string"},"detail":{"type":"string"}}},"Err401":{"type":"object","properties":{"errorCode":{"default":"UnauthorizedError","type":"string"},"detail":{"type":"string"}}},"Err404":{"type":"object","properties":{"errorCode":{"default":"ResourceNotFound","type":"string"},"detail":{"type":"string"}}},"Err429":{"type":"object","properties":{"errorCode":{"default":"Ratelimited","type":"string"},"detail":{"type":"string"}}},"Err500":{"type":"object","properties":{"errorCode":{"default":"InternalServerError","type":"string"},"detail":{"type":"string"}}},"Err504":{"type":"object","properties":{"errorCode":{"default":"Timeout","type":"string"},"detail":{"type":"string"}}},"ChainId":{"type":"number","enum":[1,10,56,137,8453,42161,81457,43114,42220,7777777,324,11155111]},"OrderInput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"}},"required":["token"]},"OrderOutput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"},"isFeeOutput":{"type":"boolean"},"recipient":{"type":"string"}},"required":["token"]},"CosignerData":{"type":"object","properties":{"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"inputOverride":{"type":"string"},"outputOverrides":{"type":"array","items":{"type":"string"}}}},"SettledAmount":{"type":"object","properties":{"tokenOut":{"$ref":"#/components/schemas/Address"},"amountOut":{"type":"string"},"tokenIn":{"$ref":"#/components/schemas/Address"},"amountIn":{"type":"string"}}},"OrderType":{"type":"string","enum":["DutchLimit","Dutch","Dutch_V2"]},"OrderTypeQuery":{"type":"string","enum":["Dutch","Dutch_V2","Dutch_V1_V2","Limit"]},"UniswapXOrder":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/OrderType"},"encodedOrder":{"type":"string"},"signature":{"type":"string"},"nonce":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"},"orderId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"quoteId":{"type":"string"},"swapper":{"type":"string"},"txHash":{"type":"string"},"input":{"$ref":"#/components/schemas/OrderInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/OrderOutput"}},"settledAmounts":{"type":"array","items":{"$ref":"#/components/schemas/SettledAmount"}},"cosignature":{"type":"string"},"cosignerData":{"$ref":"#/components/schemas/CosignerData"}},"required":["encodedOrder","signature","nonce","orderId","orderStatus","chainId","type"]},"SortKey":{"type":"string","enum":["createdAt"]},"OrderId":{"type":"string"},"OrderIds":{"type":"string"},"OrderStatus":{"type":"string","enum":["open","expired","error","cancelled","filled","unverified","insufficient-funds"]},"Permit":{"type":"object","properties":{"domain":{"type":"object"},"values":{"type":"object"},"types":{"type":"object"}}},"TokenProject":{"type":"object","properties":{"logo":{"$ref":"#/components/schemas/TokenProjectLogo","nullable":true},"safetyLevel":{"$ref":"#/components/schemas/SafetyLevel"},"isSpam":{"type":"boolean"}},"required":["logo","safetyLevel","isSpam"]},"TokenProjectLogo":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DutchInput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"}},"required":["startAmount","endAmount","type"]},"DutchOutput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"},"recipient":{"type":"string"}},"required":["startAmount","endAmount","token","recipient"]},"DutchOrderInfo":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"exclusivityOverrideBps":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchOrderInfoV2":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}},"cosigner":{"$ref":"#/components/schemas/Address"}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchQuote":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfo"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"DutchQuoteV2":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfoV2"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"deadlineBufferSecs":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"BridgeQuote":{"type":"object","properties":{"quoteId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"destinationChainId":{"$ref":"#/components/schemas/ChainId"},"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"quoteTimestamp":{"type":"number"},"gasPrice":{"type":"string"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasFee":{"type":"string"},"gasUseEstimate":{"type":"string"},"gasFeeUSD":{"type":"string"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"estimatedFillTimeMs":{"type":"number"}}},"SafetyLevel":{"type":"string","enum":["BLOCKED","MEDIUM_WARNING","STRONG_WARNING","VERIFIED"]},"TradeType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT"]},"Routing":{"type":"string","enum":["DUTCH_LIMIT","CLASSIC","DUTCH_V2","BRIDGE","LIMIT_ORDER"]},"Quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"},{"$ref":"#/components/schemas/BridgeQuote"}]},"CheckApprovalLPRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"pairAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"pairAddressAmount":{"type":"string"},"includeGasInfo":{"type":"boolean"}}},"CheckApprovalLPResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"token0Approval":{"$ref":"#/components/schemas/TransactionRequest"},"token1Approval":{"$ref":"#/components/schemas/TransactionRequest"},"pairAddressApproval":{"$ref":"#/components/schemas/TransactionRequest"},"batchedPermit2Data":{"$ref":"#/components/schemas/NullablePermit"},"gasFee0":{"type":"string"},"gasFee1":{"type":"string"},"gasFeePairAddress":{"type":"string"}}},"ApprovalRequest":{"type":"object","properties":{"walletAddress":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"},"includeGasInfo":{"type":"boolean","default":false},"tokenOut":{"$ref":"#/components/schemas/Address"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["walletAddress","token","amount"]},"ApprovalResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"approval":{"$ref":"#/components/schemas/TransactionRequest"},"cancel":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"cancelGasFee":{"type":"string"}},"required":["requestId","approval","cancel"]},"ClassicQuote":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"swapper":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"slippage":{"type":"number"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei. It does NOT include the additional gas for token approvals."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD. It does NOT include the additional gas for token approvals."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency. It does NOT include the additional gas for token approvals."},"route":{"type":"array","items":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/V3PoolInRoute"},{"$ref":"#/components/schemas/V2PoolInRoute"}]}}},"portionBips":{"type":"number","description":"The portion of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionAmount":{"type":"string","description":"The amount of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionRecipient":{"$ref":"#/components/schemas/Address"},"routeString":{"type":"string","description":"The route in string format."},"quoteId":{"type":"string","description":"The quote id. Used for analytics purposes."},"gasUseEstimate":{"type":"string","description":"The estimated gas use. It does NOT include the additional gas for token approvals."},"blockNumber":{"type":"string","description":"The current block number."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."},"txFailureReasons":{"type":"array","items":{"$ref":"#/components/schemas/TransactionFailureReason"}},"priceImpact":{"type":"number","description":"The impact the trade has on the market price of the pool, between 0-100 percent"}}},"WrapUnwrapQuote":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"chainId":{"$ref":"#/components/schemas/ChainId"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency."},"gasUseEstimate":{"type":"string","description":"The estimated gas use."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."}}},"TokenInRoute":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"symbol":{"type":"string"},"decimals":{"type":"string"},"buyFeeBps":{"type":"string"},"sellFeeBps":{"type":"string"}}},"V2Reserve":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/TokenInRoute"},"quotient":{"type":"string"}}},"V2PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v2-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"reserve0":{"$ref":"#/components/schemas/V2Reserve"},"reserve1":{"$ref":"#/components/schemas/V2Reserve"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"V3PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v3-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"sqrtRatioX96":{"type":"string"},"liquidity":{"type":"string"},"tickCurrent":{"type":"string"},"fee":{"type":"string"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"TransactionHash":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{64}$"},"ClassicInput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"}}},"ClassicOutput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"},"recipient":{"$ref":"#/components/schemas/Address"}}},"RequestId":{"type":"string"},"SpreadOptimization":{"type":"string","enum":["EXECUTION","PRICE"],"description":"For **Dutch Limit** orders only. When set to `EXECUTION`, quotes optimize for looser spreads at higher fill rates. When set to `PRICE`, quotes optimize for tighter spreads at lower fill rates","default":"EXECUTION"},"AutoSlippage":{"type":"string","enum":["DEFAULT"],"description":"For **Classic** swaps only. The auto slippage strategy to employ. If auto slippage is not defined then we don't compute it. If the auto slippage strategy is `DEFAULT`, then the swap will use the default slippage tolerance computation. You cannot define auto slippage and slippage tolerance at the same time. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","default":"undefined"},"RoutingPreference":{"type":"string","description":"The routing preference determines which protocol to use for the swap. If the routing preference is `UNISWAPX`, then the swap will be routed through the UniswapX Dutch Auction Protocol. If the routing preference is `CLASSIC`, then the swap will be routed through the Classic Protocol. If the routing preference is `BEST_PRICE`, then the swap will be routed through the protocol that provides the best price. When `UNIXWAPX_V2` is passed, the swap will be routed through the UniswapX V2 Dutch Auction Protocol. When `V3_ONLY` is passed, the swap will be routed ONLY through the Uniswap V3 Protocol. When `V2_ONLY` is passed, the swap will be routed ONLY through the Uniswap V2 Protocol.","enum":["CLASSIC","UNISWAPX","BEST_PRICE","BEST_PRICE_V2","UNISWAPX_V2","V3_ONLY","V2_ONLY"],"default":"BEST_PRICE"},"ProtocolItems":{"type":"string","enum":["V2","V3","V4","UNISWAPX","UNISWAPX_V2"]},"TransactionRequest":{"type":"object","properties":{"to":{"$ref":"#/components/schemas/Address"},"from":{"$ref":"#/components/schemas/Address"},"data":{"type":"string","description":"The calldata for the transaction."},"value":{"type":"string","description":"The value of the transaction in terms of wei in hex format."},"gasLimit":{"type":"string"},"chainId":{"type":"integer"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasPrice":{"type":"string"}},"required":["to","from","data","value","chainId"]},"TransactionFailureReason":{"type":"string","enum":["SIMULATION_ERROR","UNSUPPORTED_SIMULATION"]},"SwapSafetyMode":{"type":"string","enum":["SAFE"],"description":"The safety mode determines the safety level of the swap. If the safety mode is `SAFE`, then the swap will include a SWEEP for the native token."},"IndicativeQuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut"]},"IndicativeQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"input":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"output":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"type":{"$ref":"#/components/schemas/TradeType"}},"required":["requestId","input","output","type"]},"CreateLPPositionRequest":{"type":"object","properties":{"positionConfig":{"$ref":"#/components/schemas/PositionConfig"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"amount0Max":{"type":"string"},"amount1Max":{"type":"string"},"initialPrice":{"type":"string"},"signedBatchedPermit2Data":{"type":"string"},"includeGasInfo":{"type":"boolean"}}},"CreateLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"create":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IncreaseLPPositionRequest":{"type":"object","properties":{"positionConfigId":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"amount0Max":{"type":"string"},"amount1Max":{"type":"string"},"includeGasInfo":{"type":"boolean"}}},"IncreaseLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"increase":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"ReduceLPPositionRequest":{"type":"object","properties":{"positionConfigId":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"amount0Max":{"type":"string"},"amount1Max":{"type":"string"},"collectAsWeth":{"type":"boolean"},"includeGasInfo":{"type":"boolean"}}},"ReduceLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"reduce":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"ClaimLPFeesRequest":{"type":"object","properties":{"positionConfigId":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"collectAsWeth":{"type":"boolean"},"includeGasInfo":{"type":"boolean"}}},"ClaimLPFeesResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"claim":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IndicativeQuoteToken":{"type":"object","properties":{"amount":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/Address"}}}},"parameters":{"universalRouterVersionHeader":{"name":"x-universal-router-version","in":"header","description":"The version of the Universal Router to use for the swap journey. *MUST* be consistent throughout the API calls.","required":false,"schema":{"$ref":"#/components/schemas/UniversalRouterVersion"}},"addressParam":{"name":"address","in":"path","schema":{"$ref":"#/components/schemas/Address"},"required":true},"tokenIdParam":{"name":"tokenId","in":"path","schema":{"type":"string"},"required":true},"cursorParam":{"name":"cursor","in":"query","schema":{"type":"string"},"required":false},"limitParam":{"name":"limit","in":"query","schema":{"type":"number"},"required":false},"chainIdParam":{"name":"chainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenInChainIdParam":{"name":"tokenInChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenOutChainIdParam":{"name":"tokenOutChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"tokenInParam":{"name":"tokenIn","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"tokenOutParam":{"name":"tokenOut","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"addressPathParam":{"name":"address","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"orderStatusParam":{"name":"orderStatus","in":"query","description":"Filter by order status.","required":false,"schema":{"$ref":"#/components/schemas/OrderStatus"}},"orderTypeParam":{"name":"orderType","in":"query","description":"The default orderType is Dutch_V1_V2 and will grab both Dutch and Dutch_V2 orders.","required":false,"schema":{"$ref":"#/components/schemas/OrderTypeQuery"}},"orderIdParam":{"name":"orderId","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrderId"}},"orderIdsParam":{"name":"orderIds","in":"query","required":false,"description":"ids split by commas","schema":{"$ref":"#/components/schemas/OrderIds"}},"swapperParam":{"name":"swapper","in":"query","description":"Filter by swapper address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"fillerParam":{"name":"filler","in":"query","description":"Filter by filler address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"sortKeyParam":{"name":"sortKey","in":"query","description":"Order the query results by the sort key.","required":false,"schema":{"$ref":"#/components/schemas/SortKey"}},"sortParam":{"name":"sort","in":"query","description":"Sort query. For example: `sort=gt(UNIX_TIMESTAMP)`, `sort=between(1675872827, 1675872930)`, or `lt(1675872930)`.","required":false,"schema":{"type":"string"}},"descParam":{"description":"Sort query results by sortKey in descending order.","name":"desc","in":"query","required":false,"schema":{"type":"string"}},"transactionHashesParam":{"description":"The transaction hashes.","name":"txHashes","in":"query","required":true,"style":"form","explode":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/TransactionHash"}}}},"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"security":[{"apiKey":[]}]} \ No newline at end of file diff --git a/packages/uniswap/src/data/tradingApi/modifyTradingApiTypes.mts b/packages/uniswap/src/data/tradingApi/modifyTradingApiTypes.mts new file mode 100644 index 00000000000..1235b639238 --- /dev/null +++ b/packages/uniswap/src/data/tradingApi/modifyTradingApiTypes.mts @@ -0,0 +1,92 @@ +import { Project, SourceFile, TypeLiteralNode } from 'ts-morph' + +const project = new Project() + +const path = './src/data/tradingApi/__generated__/models' + +// Request types +const approvalRequestFile = project.addSourceFileAtPath(`${path}/ApprovalRequest.ts`) +const createSendRequestFile = project.addSourceFileAtPath(`${path}/CreateSendRequest.ts`) +const createSwapRequestFile = project.addSourceFileAtPath(`${path}/CreateSwapRequest.ts`) +const quoteRequestFile = project.addSourceFileAtPath(`${path}/QuoteRequest.ts`) +const requestFiles = [approvalRequestFile, createSendRequestFile, createSwapRequestFile, quoteRequestFile] + +// Response types +const approvalResponseFile = project.addSourceFileAtPath(`${path}/ApprovalResponse.ts`) +const createSwapResponseFile = project.addSourceFileAtPath(`${path}/CreateSwapResponse.ts`) +const createSendResponseFile = project.addSourceFileAtPath(`${path}/CreateSendResponse.ts`) +const classicQuoteFile = project.addSourceFileAtPath(`${path}/ClassicQuote.ts`) +const responseFiles = [approvalResponseFile, createSwapResponseFile, createSendResponseFile, classicQuoteFile] + +function addImport(file: SourceFile, importName: string) { + if (!file.getImportDeclaration((imp) => imp.getModuleSpecifierValue() === '../../types')) { + file.addImportDeclaration({ + namedImports: [importName], + moduleSpecifier: '../../types', + }) + } else { + const existingImport = file.getImportDeclaration((imp) => imp.getModuleSpecifierValue() === '../../types') + if ( + existingImport && + !existingImport.getNamedImports().some((namedImport) => namedImport.getName() === importName) + ) { + existingImport.addNamedImport(importName) + } + } +} + +function modifyType( + file: SourceFile, + typeName: string, + newProperties: { name: string; type: string; isOptional?: boolean }[], +) { + const typeAlias = file.getTypeAlias(typeName) + if (typeAlias) { + const typeNode = typeAlias.getTypeNode() + if (typeNode && TypeLiteralNode.isTypeLiteral(typeNode)) { + newProperties.forEach((prop) => { + const existingProperty = typeNode.getProperty(prop.name) + if (!existingProperty) { + typeNode.addProperty({ + name: prop.name, + type: prop.type, + hasQuestionToken: prop.isOptional, + }) + console.log(`Added property ${prop.name} to ${typeName}`) + } else { + console.log(`Property ${prop.name} already exists in ${typeName}`) + } + }) + } else { + console.log(`Type ${typeName} is not an object type`) + } + } else { + console.log(`Type ${typeName} not found`) + } +} + +// Modify the request interfaces +requestFiles.forEach((file) => { + addImport(file, 'GasStrategy') + modifyType(file, file.getBaseName().replace('.ts', ''), [ + { name: 'gasStrategies', type: 'GasStrategy[]', isOptional: true }, + ]) +}) + +// Modify the response interfaces +responseFiles.forEach((file) => { + addImport(file, 'GasEstimate') + modifyType(file, file.getBaseName().replace('.ts', ''), [ + { name: 'gasEstimates', type: 'GasEstimate[]', isOptional: true }, + ]) +}) + +// Save the changes +requestFiles.forEach((file) => { + file.saveSync() +}) +responseFiles.forEach((file) => { + file.saveSync() +}) + +console.log('Trading API types have been updated') diff --git a/packages/uniswap/src/data/tradingApi/types.ts b/packages/uniswap/src/data/tradingApi/types.ts new file mode 100644 index 00000000000..ccc73569b90 --- /dev/null +++ b/packages/uniswap/src/data/tradingApi/types.ts @@ -0,0 +1,35 @@ +// These types are used in the gas estimation improvement experiment. +// They are internal to uniswap, so they are not declared in the Trading API public definition. +// Once the experiment is complete, we can remove them easily or add them to the public API definition. + +export enum FeeType { + LEGACY = 'legacy', + EIP1559 = 'eip1559', +} + +export interface GasStrategy { + limitInflationFactor: number + priceInflationFactor: number + percentileThresholdFor1559Fee: number + minPriorityFeeGwei?: number | null + maxPriorityFeeGwei?: number | null +} + +export interface GasEstimateLegacy { + gasPrice: string + gasLimit: string + type: FeeType.LEGACY + strategy: GasStrategy + gasFee: string +} + +export interface GasEstimateEip1559 { + maxFeePerGas: string + maxPriorityFeePerGas: string + gasLimit: string + type: FeeType.EIP1559 + strategy: GasStrategy + gasFee: string +} + +export type GasEstimate = GasEstimateLegacy | GasEstimateEip1559 diff --git a/packages/uniswap/src/data/types.ts b/packages/uniswap/src/data/types.ts index 1d83dcb05ee..c0195ef55e4 100644 --- a/packages/uniswap/src/data/types.ts +++ b/packages/uniswap/src/data/types.ts @@ -1,7 +1,8 @@ import { QueryResult } from '@apollo/client' - +import { ApolloError } from '@apollo/client/errors' // Query result does not have a refetch property so add it here in case it needs to get returned -export type GqlResult = Pick, 'data' | 'loading' | 'error'> & +export type GqlResult = Pick, 'data' | 'loading'> & Partial, 'networkStatus'>> & { refetch?: () => void // TODO: [MOB-222] figure out the proper type for this from a QueryResult + error?: ApolloError | Error } diff --git a/packages/uniswap/src/entities/assets.ts b/packages/uniswap/src/entities/assets.ts index affffaec241..4331aee6848 100644 --- a/packages/uniswap/src/entities/assets.ts +++ b/packages/uniswap/src/entities/assets.ts @@ -1,10 +1,10 @@ -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export type TradeableAsset = CurrencyAsset | NFTAsset interface BaseTradeableAsset { address: Address - chainId: WalletChainId + chainId: UniverseChainId type: AssetType } diff --git a/packages/uniswap/src/extension/useIsChromeWindowFocused.ts b/packages/uniswap/src/extension/useIsChromeWindowFocused.ts index 64d4da6e0fa..432f8ef6be0 100644 --- a/packages/uniswap/src/extension/useIsChromeWindowFocused.ts +++ b/packages/uniswap/src/extension/useIsChromeWindowFocused.ts @@ -9,7 +9,7 @@ export function useIsChromeWindowFocused(): boolean { useCallback(async () => { if (!isExtension) { // This hook is ignored and always returns `true` when not in the Extension. - return + return undefined } const onFocusChangedListener = async (): Promise => { @@ -39,7 +39,7 @@ export function useIsChromeWindowFocusedWithTimeout(timeoutInMs: number): boolea useEffect(() => { if (isFocused) { setIsFocusedWithTimeout(true) - return + return undefined } const timeout = setTimeout(() => setIsFocusedWithTimeout(false), timeoutInMs) diff --git a/packages/uniswap/src/features/address/ExplorerView.tsx b/packages/uniswap/src/features/address/ExplorerView.tsx index 2066a52e2e8..beddbadf5d3 100644 --- a/packages/uniswap/src/features/address/ExplorerView.tsx +++ b/packages/uniswap/src/features/address/ExplorerView.tsx @@ -31,7 +31,7 @@ export function ExplorerView({ currency, modalName }: { currency: Currency; moda - + {explorerLink} diff --git a/packages/uniswap/src/features/bridging/hooks/useSwappableTokenWithHighestBalance.ts b/packages/uniswap/src/features/bridging/hooks/useSwappableTokenWithHighestBalance.ts new file mode 100644 index 00000000000..a05af1fa69b --- /dev/null +++ b/packages/uniswap/src/features/bridging/hooks/useSwappableTokenWithHighestBalance.ts @@ -0,0 +1,86 @@ +import { useMemo } from 'react' +import { useTradingApiSwappableTokensQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery' +import { tradingApiSwappableTokenToCurrencyInfo } from 'uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo' +import { GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__' +import { CurrencyInfo, PortfolioBalance } from 'uniswap/src/features/dataApi/types' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { + getTokenAddressFromChainForTradingApi, + toTradingApiSupportedChainId, +} from 'uniswap/src/features/transactions/swap/utils/tradingApi' +import { WalletChainId } from 'uniswap/src/types/chains' +import { logger } from 'utilities/src/logger/logger' + +export function useSwappableTokenWithHighestBalance({ + currencyAddress, + currencyChainId, + otherChainBalances, +}: { + currencyAddress: Address + currencyChainId: WalletChainId + otherChainBalances: PortfolioBalance[] | null +}): + | { + token: GetSwappableTokensResponse['tokens'][number] + balance: PortfolioBalance + currencyInfo: CurrencyInfo + } + | undefined { + const isBridgingEnabled = useFeatureFlag(FeatureFlags.Bridging) + + const tokenIn = currencyAddress ? getTokenAddressFromChainForTradingApi(currencyAddress, currencyChainId) : undefined + const tokenInChainId = toTradingApiSupportedChainId(currencyChainId) + + const { data: swappableTokens } = useTradingApiSwappableTokensQuery({ + params: + otherChainBalances && otherChainBalances?.length > 0 && tokenIn && tokenInChainId && isBridgingEnabled + ? { + tokenIn, + tokenInChainId, + } + : undefined, + }) + + return useMemo(() => { + if (!otherChainBalances || !swappableTokens?.tokens) { + return undefined + } + + const tokenWithHighestBalance = swappableTokens.tokens.reduce< + ReturnType | undefined + >((currentHighest, token) => { + const balance = otherChainBalances.find((b) => b.currencyInfo.currency.chainId === token.chainId) + + if (!balance?.balanceUSD) { + return currentHighest + } + + if ( + !currentHighest || + !currentHighest.balance.balanceUSD || + balance.balanceUSD > currentHighest.balance.balanceUSD + ) { + const currencyInfo = tradingApiSwappableTokenToCurrencyInfo(token) + + if (!currencyInfo) { + logger.error(new Error('Failed to convert swappable token to currency info'), { + tags: { file: 'TokenDetailsScreen.tsx', function: 'useSwappableTokenWithHighestBalance' }, + extra: { token }, + }) + return currentHighest + } + + return { + token, + balance, + currencyInfo, + } + } + + return currentHighest + }, undefined) + + return tokenWithHighestBalance + }, [otherChainBalances, swappableTokens]) +} diff --git a/packages/uniswap/src/features/dataApi/balances.ts b/packages/uniswap/src/features/dataApi/balances.ts index 2e81eb44a4f..2170c13a395 100644 --- a/packages/uniswap/src/features/dataApi/balances.ts +++ b/packages/uniswap/src/features/dataApi/balances.ts @@ -88,7 +88,7 @@ export function usePortfolioBalances({ const formattedData = useMemo(() => { if (!balancesForAddress) { - return + return undefined } const byId: Record = {} @@ -102,8 +102,8 @@ export function usePortfolioBalances({ quantity, isHidden, } = balance || {} - const { address: tokenAddress, chain, decimals, symbol, project } = token || {} - const { name, logoUrl, isSpam, safetyLevel } = project || {} + const { name, address: tokenAddress, chain, decimals, symbol, project } = token || {} + const { logoUrl, isSpam, safetyLevel } = project || {} const chainId = fromGraphQLChain(chain) // require all of these fields to be defined @@ -200,7 +200,7 @@ export function usePortfolioTotalValue({ const formattedData = useMemo(() => { if (!portfolioForAddress) { - return + return undefined } return { diff --git a/packages/uniswap/src/features/dataApi/searchTokens.ts b/packages/uniswap/src/features/dataApi/searchTokens.ts index 53205aba45e..03f5306f205 100644 --- a/packages/uniswap/src/features/dataApi/searchTokens.ts +++ b/packages/uniswap/src/features/dataApi/searchTokens.ts @@ -26,7 +26,7 @@ export function useSearchTokens( const formattedData = useMemo(() => { if (!data || !data.searchTokens) { - return + return undefined } return data.searchTokens diff --git a/packages/uniswap/src/features/dataApi/tokenProjects.ts b/packages/uniswap/src/features/dataApi/tokenProjects.ts index 464211f3347..92abf8baa48 100644 --- a/packages/uniswap/src/features/dataApi/tokenProjects.ts +++ b/packages/uniswap/src/features/dataApi/tokenProjects.ts @@ -18,7 +18,7 @@ export function useTokenProjects(currencyIds: CurrencyId[]): GqlResult { if (!data || !data.tokenProjects) { - return + return undefined } return tokenProjectToCurrencyInfos(data.tokenProjects) diff --git a/packages/uniswap/src/features/dataApi/topTokens.ts b/packages/uniswap/src/features/dataApi/topTokens.ts index 8e6597fc83a..3acd0ea1735 100644 --- a/packages/uniswap/src/features/dataApi/topTokens.ts +++ b/packages/uniswap/src/features/dataApi/topTokens.ts @@ -27,7 +27,7 @@ export function usePopularTokens(chainFilter: UniverseChainId): GqlResult { if (!data || !data.topTokens) { - return + return undefined } return data.topTokens diff --git a/packages/uniswap/src/features/dataApi/utils.test.ts b/packages/uniswap/src/features/dataApi/utils.test.ts index c1f5e7e6dc1..b017ca19a4f 100644 --- a/packages/uniswap/src/features/dataApi/utils.test.ts +++ b/packages/uniswap/src/features/dataApi/utils.test.ts @@ -51,7 +51,7 @@ describe(tokenProjectToCurrencyInfos, () => { address: token.address, decimals: token.decimals, symbol: token.symbol, - name: project.name, + name: token.name ?? project.name, }), }) as CurrencyInfo @@ -145,7 +145,7 @@ describe(gqlTokenToCurrencyInfo, () => { address: token.address, decimals: token.decimals, symbol: token.symbol, - name: token.project?.name, + name: token.name, }), currencyId: `${fromGraphQLChain(token.chain)}-${token.address}`, logoUrl: token.project?.logoUrl, diff --git a/packages/uniswap/src/features/dataApi/utils.ts b/packages/uniswap/src/features/dataApi/utils.ts index a5777d6aa4b..cd09464f920 100644 --- a/packages/uniswap/src/features/dataApi/utils.ts +++ b/packages/uniswap/src/features/dataApi/utils.ts @@ -49,8 +49,8 @@ export function tokenProjectToCurrencyInfos( return tokenProjects ?.flatMap((project) => project?.tokens.map((token) => { - const { logoUrl, safetyLevel, name } = project ?? {} - const { chain, address, decimals, symbol } = token ?? {} + const { logoUrl, safetyLevel } = project ?? {} + const { name, chain, address, decimals, symbol } = token ?? {} const chainId = fromGraphQLChain(chain) if (chainFilter && chainFilter !== chainId) { @@ -163,7 +163,7 @@ export function getCurrencySafetyInfo( } export function gqlTokenToCurrencyInfo(token: NonNullable>): CurrencyInfo | null { - const { chain, address, decimals, symbol, project, feeData, protectionInfo } = token + const { name, chain, address, decimals, symbol, project, feeData, protectionInfo } = token const chainId = fromGraphQLChain(chain) const currency = buildCurrency({ @@ -171,7 +171,7 @@ export function gqlTokenToCurrencyInfo(token: NonNullable string + conversionRate?: number } const SOURCE_CURRENCY = Currency.Usd // Assuming all currency data comes from USD @@ -151,9 +152,10 @@ export function useFiatConverter({ return useMemo( () => ({ + conversionRate, convertFiatAmount: convertFiatAmountInner, convertFiatAmountFormatted: convertFiatAmountFormattedInner, }), - [convertFiatAmountFormattedInner, convertFiatAmountInner], + [conversionRate, convertFiatAmountFormattedInner, convertFiatAmountInner], ) } diff --git a/packages/uniswap/src/features/fiatOnRamp/hooks.ts b/packages/uniswap/src/features/fiatOnRamp/hooks.ts index 3ba90ee5b45..73bfbddecd2 100644 --- a/packages/uniswap/src/features/fiatOnRamp/hooks.ts +++ b/packages/uniswap/src/features/fiatOnRamp/hooks.ts @@ -51,7 +51,7 @@ export function useFormatExactCurrencyAmount(currencyAmount: string, currency: M const formatter = useLocalizationContext() if (!currencyAmount || !currency) { - return + return undefined } const formattedAmount = getFormattedCurrencyAmount(currency, currencyAmount, formatter, false, ValueType.Exact) diff --git a/packages/uniswap/src/features/gas/hooks.ts b/packages/uniswap/src/features/gas/hooks.ts index c7b094e379c..9d1eeaa40b3 100644 --- a/packages/uniswap/src/features/gas/hooks.ts +++ b/packages/uniswap/src/features/gas/hooks.ts @@ -2,12 +2,11 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { providers } from 'ethers/lib/ethers' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useConfig } from 'statsig-react' import { isWeb } from 'ui/src' import { Warning, WarningAction, WarningLabel, WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { PollingInterval } from 'uniswap/src/constants/misc' import { useGasFeeQuery } from 'uniswap/src/data/apiClients/uniswapApi/useGasFeeQuery' -import { GasEstimate, GasStrategy } from 'uniswap/src/data/tradingApi/__generated__' +import { GasEstimate, GasStrategy } from 'uniswap/src/data/tradingApi/types' import { AccountMeta } from 'uniswap/src/features/accounts/types' import { FormattedUniswapXGasFeeInfo, @@ -18,7 +17,7 @@ import { } from 'uniswap/src/features/gas/types' import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils' import { DynamicConfigs, GasStrategies, GasStrategyType } from 'uniswap/src/features/gating/configs' -import { Statsig } from 'uniswap/src/features/gating/sdk/statsig' +import { Statsig, useConfig } from 'uniswap/src/features/gating/sdk/statsig' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' @@ -229,7 +228,7 @@ export function useTransactionGasWarning({ return useMemo(() => { // if balance is already insufficient, dont need to show warning about network fee if (gasFee === undefined || balanceInsufficient || !nativeCurrencyBalance || hasGasFunds) { - return + return undefined } return { diff --git a/packages/uniswap/src/features/gas/types.ts b/packages/uniswap/src/features/gas/types.ts index 643c33d1efa..ade532f7cf8 100644 --- a/packages/uniswap/src/features/gas/types.ts +++ b/packages/uniswap/src/features/gas/types.ts @@ -1,6 +1,6 @@ import { SerializedError } from '@reduxjs/toolkit' import { FetchError } from 'uniswap/src/data/apiClients/FetchError' -import { GasEstimate, GasStrategy } from 'uniswap/src/data/tradingApi/__generated__' +import { GasEstimate, GasStrategy } from 'uniswap/src/data/tradingApi/types' import { GasFeeEstimates } from 'uniswap/src/features/transactions/types/transactionDetails' export type TransactionLegacyFeeParams = { diff --git a/packages/uniswap/src/features/gating/configs.ts b/packages/uniswap/src/features/gating/configs.ts index 0941a617502..d65273c644e 100644 --- a/packages/uniswap/src/features/gating/configs.ts +++ b/packages/uniswap/src/features/gating/configs.ts @@ -1,4 +1,4 @@ -import { GasStrategy } from 'uniswap/src/data/tradingApi/__generated__' +import { GasStrategy } from 'uniswap/src/data/tradingApi/types' /** * Dynamic Configs diff --git a/packages/uniswap/src/features/gating/experiments.ts b/packages/uniswap/src/features/gating/experiments.ts index 95e88da3765..065a82f7f6d 100644 --- a/packages/uniswap/src/features/gating/experiments.ts +++ b/packages/uniswap/src/features/gating/experiments.ts @@ -10,6 +10,11 @@ export enum Experiments { AccountCTAs = 'signin_login_connect_ctas', } +export enum ArbitrumXV2ExperimentGroup { + Test = 'Test', + Control = 'Control', +} + export enum ArbitrumXV2OpenOrderProperties { PriceImprovementBps = 'priceImprovementBps', ForceOpenOrders = 'forceOpenOrders', diff --git a/packages/uniswap/src/features/gating/flags.ts b/packages/uniswap/src/features/gating/flags.ts index 9b927664df9..e8fda6a098d 100644 --- a/packages/uniswap/src/features/gating/flags.ts +++ b/packages/uniswap/src/features/gating/flags.ts @@ -11,6 +11,7 @@ export enum FeatureFlags { IndicativeSwapQuotes, TokenProtection, SelfReportSpamNFTs, + UniswapXPriorityOrders, // Wallet FlashbotsPrivateRpc, @@ -32,6 +33,7 @@ export enum FeatureFlags { // Extension ExtensionAutoConnect, + ExtensionClaimUnitag, // Web AATestWeb, @@ -68,6 +70,7 @@ export const WEB_FEATURE_FLAG_NAMES = new Map([ [FeatureFlags.PortionFields, 'portion-fields'], [FeatureFlags.UniswapX, 'uniswapx'], [FeatureFlags.Datadog, 'datadog'], + [FeatureFlags.UniswapXPriorityOrders, 'uniswapx_priority_orders'], // Web Specific [FeatureFlags.UniversalSwap, 'universal_swap'], @@ -103,6 +106,7 @@ export const WALLET_FEATURE_FLAG_NAMES = new Map([ [FeatureFlags.IndicativeSwapQuotes, 'indicative-quotes'], [FeatureFlags.TokenProtection, 'token_protection'], [FeatureFlags.SelfReportSpamNFTs, 'self-report-spam-nfts'], + [FeatureFlags.UniswapXPriorityOrders, 'uniswapx_priority_orders'], // Wallet Specific [FeatureFlags.Datadog, 'datadog'], @@ -120,6 +124,7 @@ export const WALLET_FEATURE_FLAG_NAMES = new Map([ [FeatureFlags.FiatOffRamp, 'fiat-offramp'], // Extension Specific [FeatureFlags.ExtensionAutoConnect, 'extension-auto-connect'], + [FeatureFlags.ExtensionClaimUnitag, 'extension-claim-unitag'], ]) export enum FeatureFlagClient { diff --git a/packages/uniswap/src/features/language/LocalizationContext.tsx b/packages/uniswap/src/features/language/LocalizationContext.tsx index e768b50bec0..aa93529e887 100644 --- a/packages/uniswap/src/features/language/LocalizationContext.tsx +++ b/packages/uniswap/src/features/language/LocalizationContext.tsx @@ -5,6 +5,7 @@ import { useFiatConverter } from 'uniswap/src/features/fiatCurrency/conversion' import { useLocalizedFormatter } from 'uniswap/src/features/language/formatter' export type LocalizationContextState = { + conversionRate: ReturnType['conversionRate'] convertFiatAmount: ReturnType['convertFiatAmount'] convertFiatAmountFormatted: ReturnType['convertFiatAmountFormatted'] formatNumberOrString: ReturnType['formatNumberOrString'] @@ -18,12 +19,13 @@ export const LocalizationContext = createContext( (): LocalizationContextState => ({ + conversionRate, convertFiatAmount, convertFiatAmountFormatted, formatNumberOrString, @@ -33,6 +35,7 @@ export function LocalizationContextProvider({ children }: { children: ReactNode }), [ addFiatSymbolToNumber, + conversionRate, convertFiatAmount, convertFiatAmountFormatted, formatCurrencyAmount, diff --git a/packages/uniswap/src/features/providers/FlashbotsRpcProvider.test.ts b/packages/uniswap/src/features/providers/FlashbotsRpcProvider.test.ts index 4e9aa35312a..a533640e51a 100644 --- a/packages/uniswap/src/features/providers/FlashbotsRpcProvider.test.ts +++ b/packages/uniswap/src/features/providers/FlashbotsRpcProvider.test.ts @@ -56,6 +56,7 @@ describe('FlashbotsRpcProvider', () => { expect(connection.headers?.['X-Flashbots-Signature']).toBe(`${testAddress}:0xsignature`) return Promise.resolve(0x3) } + return undefined }) const result = await provider.getTransactionCount(testAddress, 'pending') diff --git a/packages/uniswap/src/features/search/searchHistorySlice.ts b/packages/uniswap/src/features/search/searchHistorySlice.ts index 146838ad716..c19c66e9237 100644 --- a/packages/uniswap/src/features/search/searchHistorySlice.ts +++ b/packages/uniswap/src/features/search/searchHistorySlice.ts @@ -3,6 +3,7 @@ import { SearchResult, SearchResultType } from 'uniswap/src/features/search/Sear const SEARCH_HISTORY_LENGTH = 5 +// eslint-disable-next-line consistent-return export function searchResultId(searchResult: SearchResult): string { switch (searchResult.type) { case SearchResultType.Token: diff --git a/packages/uniswap/src/features/telemetry/constants/trace.ts b/packages/uniswap/src/features/telemetry/constants/trace.ts index 898eb89d9ac..e0f94c55b83 100644 --- a/packages/uniswap/src/features/telemetry/constants/trace.ts +++ b/packages/uniswap/src/features/telemetry/constants/trace.ts @@ -9,21 +9,25 @@ export const ModalName = { AccountEdit: 'account-edit-modal', AccountEditLabel: 'account-edit--label-modal', AccountSwitcher: 'account-switcher-modal', + AcrossRoutingInfo: 'across-routing-info-modal', AddLiquidity: 'add-liquidity', AddWallet: 'add-wallet-modal', BackupReminder: 'backup-reminder-modal', BackupReminderWarning: 'backup-reminder-warning-modal', BlockedAddress: 'blocked-address', + BridgingWarning: 'bridging-warning-modal', BuyNativeToken: 'buy-native-token-modal', ChooseProfilePhoto: 'choose-profile-photo-modal', CloudBackupInfo: 'cloud-backup-info-modal', DappRequest: 'dapp-request', ENSClaimPeriod: 'ens-claim-period', EnterPassword: 'enter-password-modal', + EstimatedTimeInfo: 'estimated-time-info-modal', ExchangeTransferModal: 'exchange-transfer-modal', Experiments: 'experiments', Explore: 'explore-modal', FaceIDWarning: 'face-id-warning', + FeeTierSearch: 'fee-tier-search-modal', FOTInfo: 'fee-on-transfer', FiatCurrencySelector: 'fiat-currency-selector', FiatOnRampAggregator: 'fiat-on-ramp-aggregator', diff --git a/packages/uniswap/src/features/telemetry/types.ts b/packages/uniswap/src/features/telemetry/types.ts index 3bc86ae38a6..0c872ebc379 100644 --- a/packages/uniswap/src/features/telemetry/types.ts +++ b/packages/uniswap/src/features/telemetry/types.ts @@ -18,6 +18,7 @@ import { NavBarSearchTypes, SharedEventName, SwapEventName, + SwapPriceImpactUserResponse, SwapPriceUpdateUserResponse, WalletConnectionResult, } from '@uniswap/analytics-events' @@ -67,6 +68,8 @@ export type GasEstimateAccuracyProperties = { private_rpc?: boolean is_shadow?: boolean name?: string + out_of_gas: boolean + timed_out: boolean } export type AssetDetailsBaseProperties = { @@ -93,6 +96,7 @@ type OnboardingCompletedProps = { } export type SwapTradeBaseProperties = { + total_balances_usd?: number transactionOriginType: string // We have both `allowed_slippage` (percentage) and `allowed_slippage_basis_points` because web and wallet used to track this in different ways. // We should eventually standardize on one or the other. @@ -158,6 +162,10 @@ type UniswapXTransactionResultProperties = BaseSwapTransactionResultProperties & order_hash: string } +type BridgeSwapTransactionResultProperties = BaseSwapTransactionResultProperties & { + routing: 'BRIDGE' +} + type FailedUniswapXOrderResultProperties = Omit type TransferProperties = { @@ -188,6 +196,10 @@ export type SwapPriceUpdateActionProperties = { price_update_basis_points?: number } +export type SwapPriceImpactActionProperties = { + response: SwapPriceImpactUserResponse +} + export type InterfaceSearchResultSelectionProperties = { suggestion_type: NavBarSearchTypes query_text: string @@ -618,11 +630,16 @@ export type UniverseEventProperties = { } [SharedEventName.NAVBAR_CLICKED]: undefined [SwapEventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED]: undefined + [SwapEventName.SWAP_PRICE_IMPACT_ACKNOWLEDGED]: SwapPriceImpactActionProperties [SwapEventName.SWAP_PRICE_UPDATE_ACKNOWLEDGED]: SwapPriceUpdateActionProperties [SwapEventName.SWAP_TRANSACTION_COMPLETED]: | ClassicSwapTransactionResultProperties | UniswapXTransactionResultProperties - [SwapEventName.SWAP_TRANSACTION_FAILED]: ClassicSwapTransactionResultProperties | FailedUniswapXOrderResultProperties + | BridgeSwapTransactionResultProperties + [SwapEventName.SWAP_TRANSACTION_FAILED]: + | ClassicSwapTransactionResultProperties + | FailedUniswapXOrderResultProperties + | BridgeSwapTransactionResultProperties [SwapEventName.SWAP_DETAILS_EXPANDED]: ITraceContext | undefined [SwapEventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED]: ITraceContext [SwapEventName.SWAP_QUOTE_RECEIVED]: { @@ -746,7 +763,7 @@ export type UniverseEventProperties = { } [WalletEventName.SwapSubmitted]: ( | { - routing: 'CLASSIC' + routing: 'CLASSIC' | 'BRIDGE' transaction_hash: string } | { diff --git a/packages/uniswap/src/features/tokens/TokenWarningModal.tsx b/packages/uniswap/src/features/tokens/TokenWarningModal.tsx index cf8ce0aa258..aa2c9ce216a 100644 --- a/packages/uniswap/src/features/tokens/TokenWarningModal.tsx +++ b/packages/uniswap/src/features/tokens/TokenWarningModal.tsx @@ -1,9 +1,12 @@ import { BigNumber } from '@ethersproject/bignumber' import { useState } from 'react' +import { Trans } from 'react-i18next' import { capitalize } from 'tsafe' -import { Flex, LabeledCheckbox, Text, styled } from 'ui/src' +import { AnimateTransition, Flex, LabeledCheckbox, Text, TouchableArea, styled, useSporeColors } from 'ui/src' +import { X } from 'ui/src/components/icons/X' import { BlockaidLogo } from 'ui/src/components/logos/BlockaidLogo' -import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' +import { Modal } from 'uniswap/src/components/modals/Modal' +import { WarningModalContent } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { getAlertColor } from 'uniswap/src/components/modals/WarningModal/getAlertColor' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' @@ -19,36 +22,46 @@ import DeprecatedTokenWarningModal from 'uniswap/src/features/tokens/DeprecatedT import { TokenWarningDesignTreatment, getIsFeeRelatedWarning, - getShouldHavePluralTreatment, + getShouldHaveCombinedPluralTreatment, getTokenWarningDesignTreatment, useModalHeaderText, useModalSubtitleText, } from 'uniswap/src/features/tokens/safetyUtils' -import { Trans, useTranslation } from 'uniswap/src/i18n' +import { useTranslation } from 'uniswap/src/i18n' import { currencyId } from 'uniswap/src/utils/currencyId' import { NumberType } from 'utilities/src/format/types' +import { isMobileApp } from 'utilities/src/platform' -interface Props { +interface TokenWarningProps { + currencyInfo0: CurrencyInfo // required, primary currency + currencyInfo1?: CurrencyInfo // defined in case of 2-token warnings; i.e. possible on the web's Pool Details Page or prefilled via /swap?inputAddress=0x...&outputAddress=0x... + isInfoOnlyWarning?: boolean // if this is an informational-only warning. Hides the Reject button + shouldBeCombinedPlural?: boolean // some 2-token warnings will be combined into one plural modal (see `getShouldHaveCombinedPluralTreatment`) + hasSecondWarning?: boolean // true if this is a 2-token warning with two separate warning screens +} + +interface TokenWarningModalContentProps extends TokenWarningProps { + onRejectButton: () => void + onAcknowledgeButton: () => void +} +interface TokenWarningModalProps extends TokenWarningProps { isVisible: boolean - currencyInfo0: CurrencyInfo - currencyInfo1?: CurrencyInfo - disableAccept?: boolean // only show message and close button - onClose: () => void - onAccept: () => void + onReject?: () => void // callback on user rejecting warning (i.e., may close the modal & clear all inputs) + onToken0BlockAcknowledged?: () => void // callback containing custom behavior for a blocked token + onToken1BlockAcknowledged?: () => void + closeModalOnly: () => void // callback that purely just closes the modal + onAcknowledge: () => void } -/** - * Warning speedbump for selecting certain tokens. - */ -export default function TokenWarningModal({ - isVisible, +function TokenWarningModalContent({ currencyInfo0, currencyInfo1, - disableAccept, - onClose, - onAccept, -}: Props): JSX.Element | null { - const tokenProtectionEnabled = useFeatureFlag(FeatureFlags.TokenProtection) + isInfoOnlyWarning, + onRejectButton, + onAcknowledgeButton, + shouldBeCombinedPlural, + hasSecondWarning, +}: TokenWarningModalContentProps): JSX.Element | null { const { t } = useTranslation() const [dontShowAgain, setDontShowAgain] = useState(false) // TODO(WALL-4596): implement dismissedTokenWarnings redux @@ -57,106 +70,217 @@ export default function TokenWarningModal({ const mockWarningSeverity = WarningSeverity.Medium // FIXME(WALL-4686): temp mock var; need to dedupe warning severity usage with TokenWarningDesignTreatment - const plural = getShouldHavePluralTreatment( - currencyInfo0.currency, - currencyInfo0.safetyInfo, - currencyInfo1?.currency, - currencyInfo1?.safetyInfo, - ) - const titleText = useModalHeaderText( currencyInfo0.currency, currencyInfo0.safetyInfo, - plural ? currencyInfo1?.currency : undefined, - plural ? currencyInfo1?.safetyInfo : undefined, + shouldBeCombinedPlural ? currencyInfo1?.currency : undefined, + shouldBeCombinedPlural ? currencyInfo1?.safetyInfo : undefined, ) const subtitleText = useModalSubtitleText( currencyInfo0.currency, currencyInfo0.safetyInfo, - plural ? currencyInfo1?.currency : undefined, - plural ? currencyInfo1?.safetyInfo : undefined, + shouldBeCombinedPlural ? currencyInfo1?.currency : undefined, + shouldBeCombinedPlural ? currencyInfo1?.safetyInfo : undefined, ) if (designTreatment === TokenWarningDesignTreatment.None) { return null } - const showActionButton = !disableAccept && designTreatment !== TokenWarningDesignTreatment.Blocked + const { background: backgroundIconColor, text: titleTextColor } = getAlertColor(mockWarningSeverity) - return tokenProtectionEnabled ? ( - - - {subtitleText} + return ( + + + + {subtitleText} + + + + } + rejectText={ + // if this is an informational-only warning or a 2-token warning, we should always show the Reject / back button + // or, if a token is blocked, it should not have a Reject button, only an Acknowledge button + isInfoOnlyWarning || hasSecondWarning || designTreatment !== TokenWarningDesignTreatment.Blocked + ? t('common.button.back') + : undefined + } + acknowledgeText={ + // if this is an informational-only warning, we don't show the Acknowledge button at all + isInfoOnlyWarning + ? undefined + : // if a token is blocked & is not part of a 2-token warning, the Acknowledge button should say "Close" + designTreatment === TokenWarningDesignTreatment.Blocked && !hasSecondWarning + ? t('common.button.close') + : // otherwise, Acknowledge button should say "Continue" + t('common.button.continue') + } + icon={} // TODO(WEB-4883): re-work WarningIcon according to severity, not safety level + severity={mockWarningSeverity} + titleComponent={ + + {titleText} - - - } - closeText={ - designTreatment === TokenWarningDesignTreatment.Blocked ? t('common.button.close') : t('common.button.back') - } - confirmText={showActionButton ? t('common.button.continue') : undefined} - icon={} // TODO(WEB-4883): re-work WarningIcon according to severity, not safety level - isOpen={isVisible} - modalName={ModalName.TokenWarningModal} - severity={mockWarningSeverity} - titleComponent={ - - {titleText} - - } - onCancel={onClose} - onClose={onClose} - onConfirm={onAccept} + } + onReject={onRejectButton} + onClose={onRejectButton} + onAcknowledge={onAcknowledgeButton} + > + {isFeeRelatedWarning && currencyInfo0.currency.isToken ? ( + + ) : ( + <> + + {shouldBeCombinedPlural && currencyInfo1 && ( + + )} + + )} + + {!isFeeRelatedWarning && ( + + + }} + /> + + + )} + + {!isInfoOnlyWarning && designTreatment === TokenWarningDesignTreatment.Low && ( + // only show "Don't show this warning again" checkbox if this is an actionable modal & the token is low-severity + + {t('token.safety.warning.dontShowWarningAgain')} + + } + size="$icon.16" + gap="$spacing8" + onCheckPressed={() => setDontShowAgain((s: boolean) => !s)} + /> + )} + + + ) +} + +/** + * Warning speedbump for selecting certain tokens. + */ +export default function TokenWarningModal({ + isVisible, + currencyInfo0, + currencyInfo1, + isInfoOnlyWarning, + onReject, + onToken0BlockAcknowledged, + onToken1BlockAcknowledged, + onAcknowledge, + closeModalOnly, +}: TokenWarningModalProps): JSX.Element | null { + const tokenProtectionEnabled = useFeatureFlag(FeatureFlags.TokenProtection) + const colors = useSporeColors() + + // If BOTH tokens are blocked or BOTH are low-severity, they'll be combined into one plural modal + const combinedPlural = getShouldHaveCombinedPluralTreatment( + currencyInfo0.currency, + currencyInfo0.safetyInfo, + currencyInfo1?.currency, + currencyInfo1?.safetyInfo, + ) + const isBlocked0 = + getTokenWarningDesignTreatment(currencyInfo0.currency, currencyInfo0.safetyInfo) === + TokenWarningDesignTreatment.Blocked + const isBlocked1 = + getTokenWarningDesignTreatment(currencyInfo1?.currency, currencyInfo1?.safetyInfo) === + TokenWarningDesignTreatment.Blocked + + const [warningIndex, setWarningIndex] = useState<0 | 1>(0) + const hasSecondWarning = Boolean(!combinedPlural && currencyInfo1) + + return tokenProtectionEnabled ? ( + - {currencyInfo0.currency.isToken && (currencyInfo0.currency.sellFeeBps || currencyInfo0.currency.buyFeeBps) ? ( - - ) : ( - <> - - {plural && currencyInfo1 && ( - - )} - - )} - - {!isFeeRelatedWarning && ( - - - }} - /> - - - )} - - {showActionButton && designTreatment === TokenWarningDesignTreatment.Low && ( - - {t('token.safety.warning.dontShowWarningAgain')} + + {hasSecondWarning && ( + + {warningIndex + 1} + + {' '} + / 2 - } - size="$icon.16" - gap="$spacing8" - onCheckPressed={() => setDontShowAgain((s: boolean) => !s)} + + )} + + + + + + + + { + if (hasSecondWarning) { + setWarningIndex(1) + } else if (isBlocked0) { + // If both tokens are blocked, they'll be combined into one plural modal. See `getShouldHaveCombinedPluralTreatment`. + combinedPlural && isBlocked1 && onToken1BlockAcknowledged?.() + onToken0BlockAcknowledged?.() + closeModalOnly() + } else { + onAcknowledge() + } + }} /> - )} - + {hasSecondWarning && currencyInfo1 && ( + { + setWarningIndex(0) + }} + onAcknowledgeButton={() => { + if (isBlocked0 || isBlocked1) { + isBlocked0 && onToken0BlockAcknowledged?.() + isBlocked1 && onToken1BlockAcknowledged?.() + closeModalOnly() + } else { + onAcknowledge() + } + }} + /> + )} + + ) : ( ) } @@ -168,9 +292,8 @@ export const WarningModalInfoContainer = styled(Flex, { borderWidth: 1, borderColor: '$surface3', px: '$spacing16', - py: '$spacing12', + py: isMobileApp ? '$spacing8' : '$spacing12', alignItems: 'center', - overflow: 'hidden', flexWrap: 'nowrap', }) diff --git a/packages/uniswap/src/features/tokens/deprecatedSafetyUtils.ts b/packages/uniswap/src/features/tokens/deprecatedSafetyUtils.ts index 4d85aee6e77..0c884d2fb42 100644 --- a/packages/uniswap/src/features/tokens/deprecatedSafetyUtils.ts +++ b/packages/uniswap/src/features/tokens/deprecatedSafetyUtils.ts @@ -14,5 +14,7 @@ export function getTokenSafetyHeaderText(safetyLevel: Maybe, t: App return t('token.safetyLevel.strong.header') case SafetyLevel.Blocked: return t('token.safetyLevel.blocked.header') + default: + return undefined } } diff --git a/packages/uniswap/src/features/tokens/hooks.ts b/packages/uniswap/src/features/tokens/hooks.ts index 2b62b40116d..e99d3011053 100644 --- a/packages/uniswap/src/features/tokens/hooks.ts +++ b/packages/uniswap/src/features/tokens/hooks.ts @@ -20,7 +20,7 @@ export function usePopularTokens(): { const popularTokens = useMemo(() => { if (!data || !data.topTokens) { - return + return undefined } // special case to replace weth with eth because the backend does not return eth data @@ -32,7 +32,7 @@ export function usePopularTokens(): { return data.topTokens .map((token) => { if (!token) { - return + return undefined } const isWeth = areAddressesEqual(token.address, wethAddress) && token?.chain === Chain.Ethereum diff --git a/packages/uniswap/src/features/tokens/safetyUtils.test.ts b/packages/uniswap/src/features/tokens/safetyUtils.test.ts index dd19e1c2e28..cfc0a454976 100644 --- a/packages/uniswap/src/features/tokens/safetyUtils.test.ts +++ b/packages/uniswap/src/features/tokens/safetyUtils.test.ts @@ -3,7 +3,7 @@ import { ProtectionResult } from 'uniswap/src/data/graphql/uniswap-data-api/__ge import { AttackType, SafetyInfo, TokenList } from 'uniswap/src/features/dataApi/types' import { TokenWarningDesignTreatment, - getShouldHavePluralTreatment, + getShouldHaveCombinedPluralTreatment, getTokenWarningDesignTreatment, useModalHeaderText, useModalSubtitleText, @@ -95,18 +95,20 @@ describe('safetyUtils', () => { describe('getShouldHavePluralTreatment', () => { it('should return false when only one currency is provided', () => { - expect(getShouldHavePluralTreatment(mockCurrency, mockSafetyInfo)).toBe(false) + expect(getShouldHaveCombinedPluralTreatment(mockCurrency, mockSafetyInfo)).toBe(false) }) it('should return true when both currencies have Low warning', () => { const lowSafetyInfo = { ...mockSafetyInfo, tokenList: TokenList.NonDefault } - expect(getShouldHavePluralTreatment(mockCurrency, lowSafetyInfo, mockCurrency, lowSafetyInfo)).toBe(true) + expect(getShouldHaveCombinedPluralTreatment(mockCurrency, lowSafetyInfo, mockCurrency, lowSafetyInfo)).toBe(true) }) it('should return false when one has low warning and the other has high warning', () => { const lowSafetyInfo = { ...mockSafetyInfo, tokenList: TokenList.NonDefault } const highSafetyInfo = { ...mockSafetyInfo, protectionResult: ProtectionResult.Malicious } - expect(getShouldHavePluralTreatment(mockCurrency, lowSafetyInfo, mockCurrency, highSafetyInfo)).toBe(false) + expect(getShouldHaveCombinedPluralTreatment(mockCurrency, lowSafetyInfo, mockCurrency, highSafetyInfo)).toBe( + false, + ) }) }) diff --git a/packages/uniswap/src/features/tokens/safetyUtils.ts b/packages/uniswap/src/features/tokens/safetyUtils.ts index 0d2a714c6d6..e53be53ea68 100644 --- a/packages/uniswap/src/features/tokens/safetyUtils.ts +++ b/packages/uniswap/src/features/tokens/safetyUtils.ts @@ -89,6 +89,7 @@ export function getIsFeeRelatedWarning(currency?: Currency, safetyInfo?: Maybe, @@ -124,7 +125,7 @@ export function getTokenWarningDesignTreatment( // Only combine into one plural-languaged modal if there are two tokens prefilled at the same time, and BOTH are low or BOTH are blocked // i.e. interface PDP, or interface prefilled via URL `?inputCurrency=0x...&outputCurrency=0x...` -export function getShouldHavePluralTreatment( +export function getShouldHaveCombinedPluralTreatment( currency0: Currency, safetyInfo0?: Maybe, currency1?: Currency, @@ -144,13 +145,14 @@ export function getShouldHavePluralTreatment( return plural ?? false } +// eslint-disable-next-line consistent-return export function useModalHeaderText( currency0: Currency, safetyInfo0?: Maybe, currency1?: Currency, safetyInfo1?: Maybe, ): string | null { - const shouldHavePluralTreatment = getShouldHavePluralTreatment(currency0, safetyInfo0, currency1, safetyInfo1) + const shouldHavePluralTreatment = getShouldHaveCombinedPluralTreatment(currency0, safetyInfo0, currency1, safetyInfo1) if (!shouldHavePluralTreatment && (currency1 || safetyInfo1)) { throw new Error('Should only combine into one plural-languaged modal if BOTH are low or BOTH are blocked') } @@ -192,7 +194,7 @@ export function useModalHeaderText( } } -// eslint-disable-next-line @typescript-eslint/no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars, consistent-return function useCardHeaderText(currency: Currency, safetyInfo?: Maybe): string | null { const { t } = useTranslation() const tokenProtectionWarning = getTokenProtectionWarning(currency, safetyInfo) @@ -232,7 +234,7 @@ export function useModalSubtitleText( currency1?: Currency, safetyInfo1?: Maybe, ): string | null { - const shouldHavePluralTreatment = getShouldHavePluralTreatment(currency0, safetyInfo0, currency1, safetyInfo1) + const shouldHavePluralTreatment = getShouldHaveCombinedPluralTreatment(currency0, safetyInfo0, currency1, safetyInfo1) if (!shouldHavePluralTreatment && (currency1 || safetyInfo1)) { throw new Error('Should only combine into one plural-languaged modal if BOTH are low or BOTH are blocked') } @@ -255,6 +257,7 @@ export function useModalSubtitleText( type: NumberType.Percentage, }) + // eslint-disable-next-line consistent-return const warningCopy = ((): string | null => { switch (tokenProtectionWarning) { case TokenProtectionWarning.MaliciousHoneypot: @@ -313,7 +316,7 @@ export function useModalSubtitleText( return warningCopy } -// eslint-disable-next-line @typescript-eslint/no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars, consistent-return function useCardSubtitleText(currency: Currency, safetyInfo?: Maybe): string | null { const { t } = useTranslation() const { formatNumberOrString } = useLocalizationContext() diff --git a/packages/uniswap/src/features/tokens/slice/hooks.ts b/packages/uniswap/src/features/tokens/slice/hooks.ts index 572a58d845e..af0c3c863bd 100644 --- a/packages/uniswap/src/features/tokens/slice/hooks.ts +++ b/packages/uniswap/src/features/tokens/slice/hooks.ts @@ -26,7 +26,7 @@ export function useDismissedTokenWarnings(info: Maybe } else { // handle tokens if (info?.isToken) { - return dispatch(dismissTokenWarning({ token: serializeToken(info) })) + dispatch(dismissTokenWarning({ token: serializeToken(info) })) } } }, [isBasicInfo, info, dispatch]) diff --git a/packages/uniswap/src/features/tokens/useCurrencyInfo.ts b/packages/uniswap/src/features/tokens/useCurrencyInfo.ts index a2d8772b309..e2a423c094b 100644 --- a/packages/uniswap/src/features/tokens/useCurrencyInfo.ts +++ b/packages/uniswap/src/features/tokens/useCurrencyInfo.ts @@ -17,7 +17,7 @@ export function useCurrencyInfo( return useMemo(() => { if (!data?.token || !_currencyId) { - return + return undefined } return gqlTokenToCurrencyInfo(data.token) diff --git a/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPad.native.tsx b/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPad.native.tsx index 612f4cfb0ef..f13fa6cbe4b 100644 --- a/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPad.native.tsx +++ b/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPad.native.tsx @@ -30,7 +30,8 @@ export const DecimalPad = memo(function DecimalPad({ disabledKeys = {}, maxHeight, onKeyPress, - onKeyLongPress, + onKeyLongPressStart, + onKeyLongPressEnd, onReady, onTriggerInputShakeAnimation, }: DecimalPadProps): JSX.Element { @@ -146,7 +147,8 @@ export const DecimalPad = memo(function DecimalPad({ // Because of this, we don't set the `disabled` prop on the number keys so we can trigger the `onPress` event. disabled={disabled || (isKeyDisabled && !shouldTriggerShake)} sizeMultiplier={sizeMultiplier} - onLongPress={shouldTriggerShake ? onTriggerInputShakeAnimation : onKeyLongPress} + onLongPressStart={shouldTriggerShake ? onTriggerInputShakeAnimation : onKeyLongPressStart} + onLongPressEnd={shouldTriggerShake ? undefined : onKeyLongPressEnd} onPress={shouldTriggerShake ? onTriggerInputShakeAnimation : onKeyPress} /> ) @@ -161,7 +163,8 @@ type KeyButtonProps = KeyProps & { disabled?: boolean sizeMultiplier: SizeMultiplier onPress?: (label: KeyLabel, action: KeyAction) => void - onLongPress?: (label: KeyLabel, action: KeyAction) => void + onLongPressStart?: (label: KeyLabel, action: KeyAction) => void + onLongPressEnd?: (label: KeyLabel, action: KeyAction) => void } const animationOptions = { duration: KEY_PRESS_ANIMATION_DURATION_MS } @@ -172,7 +175,8 @@ const KeyButton = memo(function KeyButton({ label, sizeMultiplier, onPress, - onLongPress, + onLongPressStart, + onLongPressEnd, }: KeyButtonProps): JSX.Element { const { decimalSeparator } = useAppFiatCurrencyInfo() const { hapticFeedback } = useHapticFeedback() @@ -181,24 +185,24 @@ const KeyButton = memo(function KeyButton({ const opacity = useSharedValue(1) const handlePress = useCallback(async (): Promise => { - if (disabled) { - return - } onPress?.(label, action) scale.value = withSequence(withTiming(1.3, animationOptions), withTiming(1, animationOptions)) opacity.value = withSequence(withTiming(0.75, animationOptions), withTiming(1, animationOptions)) await hapticFeedback.impact() - }, [action, disabled, hapticFeedback, label, onPress, opacity, scale]) + }, [action, hapticFeedback, label, onPress, opacity, scale]) - const handleLongPress = useCallback((): void => { - if (disabled) { - return - } - onLongPress?.(label, action) - }, [action, disabled, label, onLongPress]) + const handleLongPressStart = useCallback((): void => { + onLongPressStart?.(label, action) + }, [action, label, onLongPressStart]) + + const handleLongPressEnd = useCallback((): void => { + onLongPressEnd?.(label, action) + }, [action, label, onLongPressEnd]) - const tap = Gesture.Tap().onBegin(handlePress) - const longPress = Gesture.LongPress().onStart(handleLongPress) + // We use `onBegin` because we want to react as soon as the user touches the key, not when they release it. + const tap = Gesture.Tap().onBegin(handlePress).enabled(!disabled) + // We use `onStart` because we want to start reacting to this event when the long press is actually detected. + const longPress = Gesture.LongPress().onStart(handleLongPressStart).onEnd(handleLongPressEnd).enabled(!disabled) const composedGesture = Gesture.Simultaneous(tap, longPress) const color = disabled ? '$neutral3' : '$neutral1' diff --git a/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPadInput.tsx b/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPadInput.tsx index 3d2edf47537..bb641afc7cf 100644 --- a/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPadInput.tsx +++ b/packages/uniswap/src/features/transactions/DecimalPadInput/DecimalPadInput.tsx @@ -1,4 +1,14 @@ -import { RefObject, forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' +import { + RefObject, + forwardRef, + memo, + useCallback, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react' import { Flex } from 'ui/src' import { TextInputProps } from 'uniswap/src/components/input/TextInput' import { DecimalPad } from 'uniswap/src/features/transactions/DecimalPadInput/DecimalPad' @@ -7,6 +17,9 @@ import type { LayoutChangeEvent } from 'react-native' import { KeyAction, KeyLabel } from 'uniswap/src/features/transactions/DecimalPadInput/types' import { maxDecimalsReached } from 'utilities/src/format/truncateToMaxDecimals' +const LONG_PRESS_DELETE_INTERVAL_MS = 20 +const LONG_PRESS_DELETE_INTERVAL_DELIMITER_MS = 750 + type DisableKeyCondition = (value: string) => boolean type DecimalPadInputProps = { @@ -186,24 +199,85 @@ export const DecimalPadInput = memo( [disabled, handleInsert, handleDelete], ) - const onLongPress = useCallback( + const deletingTimeout = useRef | undefined>(undefined) + const stopDeleting = useCallback(() => clearTimeout(deletingTimeout.current), []) + + const onLongPressStart = useCallback( + (_: KeyLabel, action: KeyAction) => { + if (disabled || action !== KeyAction.Delete) { + return + } + + // We delete one character at a time until we've deleted either half of the input text or more than 5 characters, + // and then we start deleting by "word" (ie. up until the next decimal or thousand separator). + + const initialAmountLength = valueRef.current.length + + const deleteWithTimeout = (): void => { + const start = getCurrentSelection().start ?? valueRef.current.length + const isCursorAtTheEnd = start === valueRef.current.length + + const hasDeletedMoreThanHalfCharacters = valueRef.current.length <= initialAmountLength / 2 + const hasDeletedMoreThanFiveCharacters = initialAmountLength - valueRef.current.length >= 5 + + // If we haven't deleted more than half of the input or more than 5 characters, we delete one character at a time. + if (!isCursorAtTheEnd || !(hasDeletedMoreThanHalfCharacters || hasDeletedMoreThanFiveCharacters)) { + handleDelete() + deletingTimeout.current = setTimeout(deleteWithTimeout, LONG_PRESS_DELETE_INTERVAL_MS) + return + } + + const nextDelimiterPosition = Math.max( + valueRef.current.lastIndexOf('.'), + valueRef.current.lastIndexOf(','), + valueRef.current.lastIndexOf(' '), + ) + + // If we found a thousand or decimal separator, we delete up until that delimiter. + if (nextDelimiterPosition > 0) { + resetSelection({ start: nextDelimiterPosition, end: nextDelimiterPosition }) + updateValue(valueRef.current.slice(0, nextDelimiterPosition)) + + // When we delete by delimiter, we want to have a slightly longer delay so the user has enough time to stop long pressing. + deletingTimeout.current = setTimeout(deleteWithTimeout, LONG_PRESS_DELETE_INTERVAL_DELIMITER_MS) + return + } + + // If we've already deleted more than half of the input and there are no more delimiters to delete by, we delete everything. + resetSelection({ start: 0, end: 0 }) + updateValue('') + return + } + + deleteWithTimeout() + }, + [disabled, getCurrentSelection, handleDelete, resetSelection, updateValue, valueRef], + ) + + const onLongPressEnd = useCallback( (_: KeyLabel, action: KeyAction) => { if (disabled || action !== KeyAction.Delete) { return } - resetSelection({ start: 0, end: 0 }) - updateValue('') + stopDeleting() }, - [disabled, updateValue, resetSelection], + [disabled, stopDeleting], ) + useEffect(() => { + // Clear the interval when the component unmounts. + // This shouldn't be necessary, but it's a good practice to avoid potential issues with `onLongPressEnd` not firing in some unknown edge case. + return () => stopDeleting() + }, [stopDeleting]) + return ( > maxHeight: number | null onKeyPress?: (label: KeyLabel, action: KeyAction) => void - onKeyLongPress?: (label: KeyLabel, action: KeyAction) => void + onKeyLongPressStart?: (label: KeyLabel, action: KeyAction) => void + onKeyLongPressEnd?: (label: KeyLabel, action: KeyAction) => void onReady: () => void onTriggerInputShakeAnimation: () => void } diff --git a/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx b/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx index e2e57ef9a5d..31973e8474f 100644 --- a/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx +++ b/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx @@ -16,6 +16,8 @@ import { FeeOnTransferFeeGroupProps, } from 'uniswap/src/features/transactions/TransactionDetails/FeeOnTransferFee' import { SwapFee } from 'uniswap/src/features/transactions/TransactionDetails/SwapFee' +import { AcrossRoutingInfo } from 'uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo' +import { EstimatedTime } from 'uniswap/src/features/transactions/swap/review/EstimatedTime' import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { SwapFeeInfo } from 'uniswap/src/features/transactions/swap/types/trade' import { WalletChainId } from 'uniswap/src/types/chains' @@ -33,6 +35,8 @@ interface TransactionDetailsProps { onShowWarning?: () => void indicative?: boolean isSwap?: boolean + isBridgeTrade?: boolean + estimatedBridgingTime?: number AccountDetails?: JSX.Element transactionUSDValue?: Maybe> } @@ -52,7 +56,9 @@ export function TransactionDetails({ indicative = false, isSwap, transactionUSDValue, + isBridgeTrade, AccountDetails, + estimatedBridgingTime, }: PropsWithChildren): JSX.Element { const { t } = useTranslation() const warningColor = getAlertColor(warning?.severity) @@ -105,6 +111,7 @@ export function TransactionDetails({ {showChildren ? {children} : null} {feeOnTransferProps && } + {isSwap && isBridgeTrade && } {isSwap && } + {isSwap && isBridgeTrade && } {AccountDetails} diff --git a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.web.tsx b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.web.tsx index a5fd6cbffe3..30b53e51f40 100644 --- a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.web.tsx +++ b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.web.tsx @@ -18,6 +18,7 @@ export function TransactionModal({ onClose, openWalletRestoreModal, walletNeedsRestore, + swapRedirectCallback, modalName, }: TransactionModalProps): JSX.Element { const [screen, setScreen] = useState(TransactionScreen.Form) @@ -30,6 +31,7 @@ export function TransactionModal({ walletNeedsRestore={walletNeedsRestore} screen={screen} setScreen={setScreen} + swapRedirectCallback={swapRedirectCallback} onClose={onClose} > {children} diff --git a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalContext.tsx b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalContext.tsx index 59aa9dae28d..769631f993d 100644 --- a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalContext.tsx +++ b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalContext.tsx @@ -1,14 +1,31 @@ +import { Currency } from '@uniswap/sdk-core' import { createContext, PropsWithChildren, useContext, useMemo } from 'react' /* eslint-disable no-restricted-imports */ import type { StyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet' import type { ViewStyle } from 'react-native/Libraries/StyleSheet/StyleSheetTypes' import { AuthTrigger } from 'uniswap/src/features/auth/types' +import { UniverseChainId } from 'uniswap/src/types/chains' +import { CurrencyField } from 'uniswap/src/types/currency' export enum TransactionScreen { Form = 'Form', Review = 'Review', } +export type SwapRedirectFn = ({ + inputCurrency, + outputCurrency, + typedValue, + independentField, + chainId, +}: { + inputCurrency?: Currency + outputCurrency?: Currency + typedValue?: string + independentField?: CurrencyField + chainId: UniverseChainId +}) => void + export type TransactionModalContextState = { bottomSheetViewStyles: StyleProp openWalletRestoreModal?: () => void @@ -18,6 +35,7 @@ export type TransactionModalContextState = { authTrigger?: AuthTrigger screen: TransactionScreen setScreen: (newScreen: TransactionScreen) => void + swapRedirectCallback?: SwapRedirectFn } export const TransactionModalContext = createContext(undefined) @@ -32,6 +50,7 @@ export function TransactionModalContextProvider({ walletNeedsRestore, screen, setScreen, + swapRedirectCallback, }: PropsWithChildren): JSX.Element { const state = useMemo( (): TransactionModalContextState => ({ @@ -42,6 +61,7 @@ export function TransactionModalContextProvider({ openWalletRestoreModal, screen, setScreen, + swapRedirectCallback, walletNeedsRestore, }), [ @@ -52,6 +72,7 @@ export function TransactionModalContextProvider({ openWalletRestoreModal, screen, setScreen, + swapRedirectCallback, walletNeedsRestore, ], ) diff --git a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalProps.tsx b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalProps.tsx index f08d2ccbb17..cbbeb4f9172 100644 --- a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalProps.tsx +++ b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModalProps.tsx @@ -9,6 +9,7 @@ export type TransactionModalProps = PropsWithChildren<{ modalName: ModalNameType onClose: () => void openWalletRestoreModal?: TransactionModalContextState['openWalletRestoreModal'] + swapRedirectCallback?: TransactionModalContextState['swapRedirectCallback'] walletNeedsRestore?: TransactionModalContextState['walletNeedsRestore'] BiometricsIcon?: TransactionModalContextState['BiometricsIcon'] authTrigger?: TransactionModalContextState['authTrigger'] diff --git a/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts b/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts index ab6c2575a28..db77f1b613e 100644 --- a/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts +++ b/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts @@ -36,7 +36,7 @@ export function useUSDTokenUpdater({ useEffect(() => { if (!currency || !price) { - return + return undefined } const exactAmountUSD = (parseFloat(exactAmountFiat) / conversionRate).toFixed(NUM_DECIMALS_USD) diff --git a/packages/uniswap/src/features/transactions/modals/BlockedAddressModal.tsx b/packages/uniswap/src/features/transactions/modals/BlockedAddressModal.tsx index d5d837eda2a..a21e9646939 100644 --- a/packages/uniswap/src/features/transactions/modals/BlockedAddressModal.tsx +++ b/packages/uniswap/src/features/transactions/modals/BlockedAddressModal.tsx @@ -9,7 +9,7 @@ export function BlockedAddressModal({ isOpen, onClose }: { isOpen: boolean; onCl return ( } isOpen={isOpen} modalName={ModalName.SwapWarning} severity={WarningSeverity.Low} title={t('account.wallet.viewOnly.title')} onClose={onDismiss} - onConfirm={onDismiss} + onAcknowledge={onDismiss} /> ) } diff --git a/packages/uniswap/src/features/transactions/selectors.ts b/packages/uniswap/src/features/transactions/selectors.ts index 7eb3681bf79..9ed4d5fc1e2 100644 --- a/packages/uniswap/src/features/transactions/selectors.ts +++ b/packages/uniswap/src/features/transactions/selectors.ts @@ -45,12 +45,12 @@ export const makeSelectAddressTransactions = (): Selector< (_: UniswapState, address: Address | null) => address, (transactions, address) => { if (!address) { - return + return undefined } const addressTransactions = transactions[address] if (!addressTransactions) { - return + return undefined } return unique(flattenObjectOfObjects(addressTransactions), (tx, _, self) => { @@ -175,6 +175,7 @@ export const makeSelectUniswapXOrder = (): Selector< } } } + return undefined }, ) // Returns a list of past recipients ordered from most to least recent diff --git a/packages/uniswap/src/features/transactions/send/types.ts b/packages/uniswap/src/features/transactions/send/types.ts index 30d4f210d5b..494b3e93664 100644 --- a/packages/uniswap/src/features/transactions/send/types.ts +++ b/packages/uniswap/src/features/transactions/send/types.ts @@ -2,13 +2,13 @@ import { AssetType } from 'uniswap/src/entities/assets' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { GQLNftAsset } from 'uniswap/src/features/nfts/types' import { BaseDerivedInfo } from 'uniswap/src/features/transactions/types/baseDerivedInfo' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' export type DerivedSendInfo = BaseDerivedInfo & { currencyTypes: { [CurrencyField.INPUT]?: AssetType } currencyInInfo?: CurrencyInfo | null - chainId: WalletChainId + chainId: UniverseChainId exactAmountFiat: string exactCurrencyField: CurrencyField.INPUT isFiatInput?: boolean diff --git a/packages/uniswap/src/features/transactions/slice.ts b/packages/uniswap/src/features/transactions/slice.ts index 0d6629f9ab0..3bf3e1031fa 100644 --- a/packages/uniswap/src/features/transactions/slice.ts +++ b/packages/uniswap/src/features/transactions/slice.ts @@ -36,6 +36,14 @@ const slice = createSlice({ assert(state?.[from]?.[chainId]?.[id], `updateTransaction: Attempted to update a missing tx with id ${id}`) state[from]![chainId]![id] = transaction }, + updateTransactionWithoutWatch: (state, { payload: transaction }: PayloadAction) => { + const { chainId, id, from } = transaction + assert( + state?.[from]?.[chainId]?.[id], + `updateTransactionWithoutWatch: Attempted to update a missing tx with id ${id}`, + ) + state[from]![chainId]![id] = transaction + }, finalizeTransaction: (state, { payload: transaction }: PayloadAction) => { const { chainId, id, status, receipt, from, hash } = transaction assert(state?.[from]?.[chainId]?.[id], `finalizeTransaction: Attempted to finalize a missing tx with id ${id}`) @@ -126,6 +134,7 @@ export const { resetTransactions, upsertFiatOnRampTransaction, updateTransaction, + updateTransactionWithoutWatch, } = slice.actions export const { reducer: transactionReducer } = slice diff --git a/packages/uniswap/src/features/transactions/swap/SwapFlow.tsx b/packages/uniswap/src/features/transactions/swap/SwapFlow.tsx index 610b14d5284..1146c0bf40a 100644 --- a/packages/uniswap/src/features/transactions/swap/SwapFlow.tsx +++ b/packages/uniswap/src/features/transactions/swap/SwapFlow.tsx @@ -24,12 +24,16 @@ import { WrapCallback } from 'uniswap/src/features/transactions/swap/types/wrapC export interface SwapFlowProps extends Omit { prefilledState?: SwapFormState customSettings?: SwapSettingConfig[] + hideHeader?: boolean + hideFooter?: boolean swapCallback: SwapCallback wrapCallback: WrapCallback } export function SwapFlow({ prefilledState, + hideHeader, + hideFooter, customSettings = [], swapCallback, wrapCallback, @@ -37,7 +41,7 @@ export function SwapFlow({ }: SwapFlowProps): JSX.Element { return ( - + diff --git a/packages/uniswap/src/features/transactions/swap/analytics.ts b/packages/uniswap/src/features/transactions/swap/analytics.ts index d48a13cb94e..4db05384506 100644 --- a/packages/uniswap/src/features/transactions/swap/analytics.ts +++ b/packages/uniswap/src/features/transactions/swap/analytics.ts @@ -43,11 +43,13 @@ export function getBaseTradeAnalyticsProperties({ trade, currencyInAmountUSD, currencyOutAmountUSD, + portfolioBalanceUsd, }: { formatter: LocalizationContextState trade: Trade currencyInAmountUSD?: Maybe> currencyOutAmountUSD?: Maybe> + portfolioBalanceUsd?: number }): SwapTradeBaseProperties { const portionAmount = getClassicQuoteFromResponse(trade?.quote)?.portionAmount @@ -64,11 +66,12 @@ export function getBaseTradeAnalyticsProperties({ const slippagePercent = percentFromFloat(trade.slippageTolerance ?? 0) return { + total_balances_usd: portfolioBalanceUsd, token_in_symbol: trade.inputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol, token_in_address: getCurrencyAddressForAnalytics(trade.inputAmount.currency), token_out_address: getCurrencyAddressForAnalytics(trade.outputAmount.currency), - price_impact_basis_points: trade.priceImpact.multiply(100).toSignificant(), + price_impact_basis_points: trade.priceImpact?.multiply(100).toSignificant(), chain_id: trade.inputAmount.currency.chainId, token_in_amount: trade.inputAmount.toExact(), token_out_amount: formatter.formatCurrencyAmount({ @@ -77,8 +80,9 @@ export function getBaseTradeAnalyticsProperties({ }), token_in_amount_usd: currencyInAmountUSD ? parseFloat(currencyInAmountUSD.toFixed(2)) : undefined, token_out_amount_usd: currencyOutAmountUSD ? parseFloat(currencyOutAmountUSD.toFixed(2)) : undefined, - allowed_slippage: parseFloat(trade.slippageTolerance.toFixed(2)), - allowed_slippage_basis_points: trade.slippageTolerance * 100, + allowed_slippage: + trade.slippageTolerance !== undefined ? parseFloat(trade.slippageTolerance.toFixed(2)) : undefined, + allowed_slippage_basis_points: trade.slippageTolerance ? trade.slippageTolerance * 100 : undefined, fee_amount: portionAmount, requestId: trade.quote?.requestId, ura_request_id: trade.quote?.requestId, @@ -139,7 +143,7 @@ export function getBaseTradeAnalyticsPropertiesFromSwapInfo({ token_out_symbol: outputCurrencyAmount?.currency.symbol, token_in_address: inputCurrencyAmount ? getCurrencyAddressForAnalytics(inputCurrencyAmount?.currency) : '', token_out_address: outputCurrencyAmount ? getCurrencyAddressForAnalytics(outputCurrencyAmount?.currency) : '', - price_impact_basis_points: derivedSwapInfo.trade.trade?.priceImpact.multiply(100).toSignificant(), + price_impact_basis_points: derivedSwapInfo.trade.trade?.priceImpact?.multiply(100)?.toSignificant(), estimated_network_fee_usd: undefined, chain_id: chainId, token_in_amount: inputCurrencyAmount?.toExact() ?? '', diff --git a/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx b/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx index 42f038b1c60..e07d3e6de4e 100644 --- a/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx +++ b/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx @@ -21,13 +21,15 @@ export type SwapFormState = { exactAmountToken?: string exactCurrencyField: CurrencyField focusOnCurrencyField?: CurrencyField - filteredChainId?: UniverseChainId + filteredChainIds: { [key in CurrencyField]?: UniverseChainId } input?: TradeableAsset output?: TradeableAsset selectingCurrencyField?: CurrencyField txId?: string isFiatMode: boolean isSubmitting: boolean + hideFooter?: boolean + hideSettings?: boolean tradeProtocolPreference: TradeProtocolPreference } @@ -40,13 +42,16 @@ type SwapFormContextState = { exactAmountFiatRef: React.MutableRefObject exactAmountTokenRef: React.MutableRefObject updateSwapForm: (newState: Partial) => void + resetSwapForm: () => void } & SwapFormState & DerivedSwapFormState -const ETH_TRADEABLE_ASSET: Readonly = { - address: getNativeAddress(UniverseChainId.Mainnet), - chainId: UniverseChainId.Mainnet, - type: AssetType.Currency, +function getDefaultInputCurrency(chainId: UniverseChainId): TradeableAsset { + return { + address: getNativeAddress(chainId), + chainId, + type: AssetType.Currency, + } } const DEFAULT_STATE: Readonly> = { @@ -54,8 +59,8 @@ const DEFAULT_STATE: Readonly> = { exactAmountToken: '', exactCurrencyField: CurrencyField.INPUT, focusOnCurrencyField: CurrencyField.INPUT, - filteredChainId: undefined, - input: ETH_TRADEABLE_ASSET, + filteredChainIds: {}, + input: getDefaultInputCurrency(UniverseChainId.Mainnet), output: undefined, isFiatMode: false, isSubmitting: false, @@ -67,9 +72,13 @@ export const SwapFormContext = createContext(u export function SwapFormContextProvider({ children, + hideFooter, + hideSettings, prefilledState, }: { children: ReactNode + hideFooter?: boolean + hideSettings?: boolean prefilledState?: SwapFormState }): JSX.Element { const amountUpdatedTimeRef = useRef(0) @@ -101,6 +110,14 @@ export function SwapFormContextProvider({ [setSwapForm, datadogEnabled], ) + const resetSwapForm = useCallback(() => { + // Reset to default state, except avoid resetting the current chain + setSwapForm((prev) => ({ + ...DEFAULT_STATE, + input: getDefaultInputCurrency(prev.output?.chainId ?? UniverseChainId.Mainnet), + })) + }, [setSwapForm]) + const [debouncedExactAmountToken, isDebouncingExactAmountToken] = useDebounceWithStatus( swapForm.exactAmountToken, SWAP_FORM_DEBOUNCE_TIME_MS, @@ -140,7 +157,7 @@ export function SwapFormContextProvider({ exactAmountTokenRef, exactCurrencyField: swapForm.exactCurrencyField, focusOnCurrencyField: swapForm.focusOnCurrencyField, - filteredChainId: swapForm.filteredChainId, + filteredChainIds: swapForm.filteredChainIds, input: swapForm.input, isFiatMode: swapForm.isFiatMode, isSubmitting: swapForm.isSubmitting, @@ -148,7 +165,10 @@ export function SwapFormContextProvider({ tradeProtocolPreference: swapForm.tradeProtocolPreference, selectingCurrencyField: swapForm.selectingCurrencyField, txId: swapForm.txId, + hideFooter, + hideSettings, updateSwapForm, + resetSwapForm, }), [ swapForm.customSlippageTolerance, @@ -157,7 +177,7 @@ export function SwapFormContextProvider({ swapForm.exactAmountToken, swapForm.exactCurrencyField, swapForm.focusOnCurrencyField, - swapForm.filteredChainId, + swapForm.filteredChainIds, swapForm.input, swapForm.isFiatMode, swapForm.isSubmitting, @@ -166,7 +186,10 @@ export function SwapFormContextProvider({ swapForm.selectingCurrencyField, swapForm.txId, derivedSwapInfo, + hideSettings, + hideFooter, updateSwapForm, + resetSwapForm, ], ) diff --git a/packages/uniswap/src/features/transactions/swap/form/SwapFormButton.tsx b/packages/uniswap/src/features/transactions/swap/form/SwapFormButton.tsx index 3ef719df817..d3313b838ea 100644 --- a/packages/uniswap/src/features/transactions/swap/form/SwapFormButton.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/SwapFormButton.tsx @@ -1,8 +1,7 @@ /* eslint-disable complexity */ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { AnimatePresence, Button, ColorTokens, Flex, SpinningLoader, Text, isWeb, useIsShortMobileDevice } from 'ui/src' -import { iconSizes } from 'ui/src/theme' +import { AnimatePresence, Button, ColorTokens, Flex, Text, isWeb, useIsShortMobileDevice } from 'ui/src' import { useAccountMeta, useUniswapContext } from 'uniswap/src/contexts/UniswapContext' import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountCTAsExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' @@ -17,7 +16,6 @@ import { import { ViewOnlyModal } from 'uniswap/src/features/transactions/modals/ViewOnlyModal' import { useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { useParsedSwapWarnings } from 'uniswap/src/features/transactions/swap/hooks/useSwapWarnings' -import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { isWrapAction } from 'uniswap/src/features/transactions/swap/utils/wrap' import { useIsBlocked } from 'uniswap/src/features/trm/hooks' import { TestID } from 'uniswap/src/test/fixtures/testIDs' @@ -33,13 +31,13 @@ export function SwapFormButton(): JSX.Element { const isShortMobileDevice = useIsShortMobileDevice() const activeAccount = useAccountMeta() - const { walletNeedsRestore, setScreen } = useTransactionModalContext() + const { walletNeedsRestore, setScreen, swapRedirectCallback } = useTransactionModalContext() const { derivedSwapInfo, isSubmitting, updateSwapForm, exactAmountFiat, exactAmountToken } = useSwapFormContext() const { blockingWarning, insufficientBalanceWarning, insufficientGasFundsWarning } = useParsedSwapWarnings() const [showViewOnlyModal, setShowViewOnlyModal] = useState(false) - const { wrapType, trade, currencies, chainId } = derivedSwapInfo + const { wrapType, trade, currencies, chainId, exactCurrencyField } = derivedSwapInfo const { isBlocked, isBlockedLoading } = useIsBlocked(activeAccount?.address) @@ -55,8 +53,16 @@ export function SwapFormButton(): JSX.Element { const { onConnectWallet } = useUniswapContext() const onReviewPress = useCallback(() => { - // Active account will only ever be undefined on web - if (!activeAccount && onConnectWallet) { + if (swapRedirectCallback) { + swapRedirectCallback({ + inputCurrency: currencies[CurrencyField.INPUT]?.currency, + outputCurrency: currencies[CurrencyField.OUTPUT]?.currency, + typedValue: exactAmountToken, + independentField: exactCurrencyField, + chainId, + }) + // Active account will only ever be undefined on web + } else if (!activeAccount && onConnectWallet) { onConnectWallet() } else if (isViewOnlyWallet) { setShowViewOnlyModal(true) @@ -64,10 +70,18 @@ export function SwapFormButton(): JSX.Element { updateSwapForm({ txId: createTransactionId() }) setScreen(TransactionScreen.Review) } - }, [activeAccount, isViewOnlyWallet, onConnectWallet, setScreen, updateSwapForm]) - - // TODO(WEB-4821): Remove uniswapx submission logic since this component will no longer be rendered during submission - const showUniswapXSubmittingUI = trade.trade && isUniswapX(trade?.trade) && isSubmitting + }, [ + activeAccount, + currencies, + onConnectWallet, + exactAmountToken, + exactCurrencyField, + chainId, + isViewOnlyWallet, + setScreen, + swapRedirectCallback, + updateSwapForm, + ]) const invalidTokenSelection = Object.values(currencies).some((currency) => !currency) const invalidAmountSelection = !exactAmountFiat && !exactAmountToken @@ -80,6 +94,9 @@ export function SwapFormButton(): JSX.Element { const isLogIn = accountsCTAExperimentGroup === AccountCTAsExperimentGroup.LogInCreateAccount const getButtonText = (): string => { + if (swapRedirectCallback) { + return t('common.getStarted') + } if (!activeAccount) { return isSignIn ? t('nav.signIn.button') : isLogIn ? t('nav.logIn.button') : t('common.connectWallet.button') } @@ -110,8 +127,16 @@ export function SwapFormButton(): JSX.Element { buttonText: string } = { backgroundColor: - !activeAccount || isSubmitting ? '$accent2' : isBlockingWithCustomMessage ? '$surface2' : '$accent1', - buttonTextColor: !activeAccount ? '$accent1' : isBlockingWithCustomMessage ? '$neutral2' : '$white', + !activeAccount || isSubmitting + ? '$accent2' + : isBlockingWithCustomMessage && !swapRedirectCallback + ? '$surface2' + : '$accent1', + buttonTextColor: !activeAccount + ? '$accent1' + : isBlockingWithCustomMessage && !swapRedirectCallback + ? '$neutral2' + : '$white', buttonText: getButtonText(), } @@ -124,8 +149,7 @@ export function SwapFormButton(): JSX.Element { pressStyle={{ backgroundColor: buttonProps.backgroundColor, opacity: 0.7 }} hoverStyle={{ backgroundColor: buttonProps.backgroundColor, opacity: 0.9 }} backgroundColor={buttonProps.backgroundColor} - disabled={!!activeAccount && reviewButtonDisabled && !isViewOnlyWallet} - icon={showUniswapXSubmittingUI ? : undefined} + disabled={!!activeAccount && reviewButtonDisabled && !isViewOnlyWallet && !swapRedirectCallback} // use opacity 1 for states with error text, because surface2 is hard to read with default disabled opacity opacity={isViewOnlyWallet ? 0.4 : isBlockingWithCustomMessage ? 1 : undefined} size={isShortMobileDevice ? 'small' : isWeb ? 'medium' : 'large'} @@ -133,13 +157,9 @@ export function SwapFormButton(): JSX.Element { width="100%" onPress={onReviewPress} > - {showUniswapXSubmittingUI ? ( - - ) : ( - - {buttonProps.buttonText} - - )} + + {buttonProps.buttonText} + setShowViewOnlyModal(false)} /> diff --git a/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx b/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx index 9d72a157080..1baae54ed31 100644 --- a/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx @@ -63,6 +63,7 @@ const ON_SELECTION_CHANGE_WAIT_TIME_MS = 500 interface SwapFormScreenProps { hideContent: boolean + hideFooter?: boolean customSettings: SwapSettingConfig[] } @@ -72,14 +73,14 @@ interface SwapFormScreenProps { */ export function SwapFormScreen({ hideContent, customSettings }: SwapFormScreenProps): JSX.Element { const { bottomSheetViewStyles } = useTransactionModalContext() - const { selectingCurrencyField } = useSwapFormContext() + const { selectingCurrencyField, hideSettings } = useSwapFormContext() const showTokenSelector = !hideContent && !!selectingCurrencyField return ( {!isInterface && /* Interface renders its own header with multiple tabs */} - + {!hideSettings && } {!hideContent && } @@ -107,6 +108,7 @@ function SwapFormContent(): JSX.Element { input, isFiatMode, output, + hideFooter, updateSwapForm, } = useSwapFormContext() @@ -437,6 +439,11 @@ function SwapFormContent(): JSX.Element { const decimalPadValueRef = decimalPadControlledField === exactCurrencyField ? exactValueRef : formattedDerivedValueRef + // If exact output will fail due to FoT tokens, the field should be disabled and un-focusable. + // Also, for bridging, the output field should be disabled since Across does not have exact in vs. exact out. + const isBridge = input && output && input?.chainId !== output?.chainId + const exactOutputDisabled = isBridge || exactOutputWillFail + const [showWarning, setShowWarning] = useState(false) const timeoutRef = useRef(null) const showTemporaryFoTWarning = (): void => { @@ -505,7 +512,7 @@ function SwapFormContent(): JSX.Element { currencyBalance={currencyBalances[CurrencyField.OUTPUT]} currencyField={CurrencyField.OUTPUT} currencyInfo={currencies[CurrencyField.OUTPUT]} - disabled={exactOutputWillFail} // If exact output will fail due to FoT tokens, the input field should be disabled and un-focusable + disabled={exactOutputDisabled} focus={focusOnCurrencyField === CurrencyField.OUTPUT} isFiatMode={isFiatMode && exactFieldIsOutput} isLoading={!exactFieldIsOutput && isSwapDataLoading} @@ -514,7 +521,7 @@ function SwapFormContent(): JSX.Element { usdValue={currencyAmountsUSDValue[CurrencyField.OUTPUT]} value={exactFieldIsOutput ? exactValue : formattedDerivedValue} valueIsIndicative={!exactFieldIsOutput && trade.indicativeTrade && !trade.trade} - onPressDisabled={showTemporaryFoTWarning} + onPressDisabled={isBridge ? undefined : showTemporaryFoTWarning} onPressIn={onFocusOutput} onSelectionChange={onOutputSelectionChange} onSetExactAmount={onSetExactAmountOutput} @@ -553,12 +560,14 @@ function SwapFormContent(): JSX.Element { )} - - - {showWarning && } - - {!showWarning && } - + {!hideFooter && ( + + + {showWarning && } + + {!showWarning && } + + )} {!isWeb && ( diff --git a/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx b/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx index 851c4dd383d..b0ffca5b5d5 100644 --- a/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx @@ -43,7 +43,7 @@ export function SwapFormSettings({ customSettings }: { customSettings: SwapSetti const rightAlignment = isMobileApp ? 24 : 4 return ( - + { + (currency: Currency, field: CurrencyField, _context: SearchContext, isBridgePair: boolean) => { const tradeableAsset: TradeableAsset = { address: currencyAddress(currency), chainId: currency.chainId, @@ -55,7 +56,7 @@ export function SwapTokenSelector({ isModalOpen }: { isModalOpen: boolean }): JS exactCurrencyField === CurrencyField.INPUT ? CurrencyField.OUTPUT : CurrencyField.INPUT newState.focusOnCurrencyField = newState.exactCurrencyField newState[otherField] = previouslySelectedTradableAsset - } else if (!chainsAreEqual) { + } else if (!chainsAreEqual && !isBridgePair) { // if new token chain changes, try to find the other token's match on the new chain const otherFieldTokenProjects = otherField === CurrencyField.INPUT ? inputTokenProjects : outputTokenProjects const otherCurrency = otherFieldTokenProjects?.data?.find( @@ -73,7 +74,20 @@ export function SwapTokenSelector({ isModalOpen }: { isModalOpen: boolean }): JS : undefined } - newState.filteredChainId = currency.chainId + if (!isBridgePair) { + // If selecting output, set the input and output chainIds + // If selecting input and output is already selected, also set the input chainId + if (field === CurrencyField.OUTPUT || !!output) { + filteredChainIds[CurrencyField.INPUT] = currency.chainId + filteredChainIds[CurrencyField.OUTPUT] = currency.chainId + // If selecting input, only set the output chainId + } else { + filteredChainIds[CurrencyField.OUTPUT] = currency.chainId + } + + newState.filteredChainIds = filteredChainIds + } + newState[field] = tradeableAsset updateSwapForm(newState) @@ -81,13 +95,23 @@ export function SwapTokenSelector({ isModalOpen }: { isModalOpen: boolean }): JS // Hide screen when done selecting. onHideTokenSelector() }, - [exactCurrencyField, input, inputTokenProjects, onHideTokenSelector, output, outputTokenProjects, updateSwapForm], + [ + exactCurrencyField, + input, + inputTokenProjects, + filteredChainIds, + onHideTokenSelector, + output, + outputTokenProjects, + updateSwapForm, + ], ) const props: TokenSelectorProps = { isModalOpen, activeAccountAddress, - chainId: filteredChainId, + chainId: filteredChainIds[selectingCurrencyField ?? CurrencyField.INPUT], + input, // token selector modal will only open on currency field selection; casting to satisfy typecheck here - we should consider refactoring the types here to avoid casting currencyField: selectingCurrencyField as CurrencyField, flow: TokenSelectorFlow.Swap, diff --git a/packages/uniswap/src/features/transactions/swap/form/footer/GasAndWarningRows.web.tsx b/packages/uniswap/src/features/transactions/swap/form/footer/GasAndWarningRows.web.tsx index e200ca3428d..e610dcf801e 100644 --- a/packages/uniswap/src/features/transactions/swap/form/footer/GasAndWarningRows.web.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/footer/GasAndWarningRows.web.tsx @@ -24,7 +24,7 @@ export function GasAndWarningRows(): JSX.Element { const { derivedSwapInfo } = useSwapFormContext() const { trade } = derivedSwapInfo - const priceImpact = trade.trade ? normalizePriceImpact(trade.trade?.priceImpact) : undefined + const priceImpact = trade.trade?.priceImpact ? normalizePriceImpact(trade.trade?.priceImpact) : undefined const [showWarningModal, setShowWarningModal] = useState(false) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useAcceptedTrade.ts b/packages/uniswap/src/features/transactions/swap/hooks/useAcceptedTrade.ts index f114c4145c8..464ee9890e8 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useAcceptedTrade.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useAcceptedTrade.ts @@ -34,7 +34,7 @@ export function useAcceptedTrade({ const onAcceptTrade = (): undefined => { if (!trade) { - return undefined + return } setAcceptedDerivedSwapInfo(derivedSwapInfo) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts index 07fd4bcdfde..b1843ae2f22 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts @@ -89,6 +89,7 @@ export function useDerivedSwapInfo({ currency: otherCurrency, }) } + return undefined }, [exactAmountToken, isWrap, otherCurrency]) const sendPortionEnabled = useFeatureFlag(FeatureFlags.PortionFields) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/usePermit2Signature.ts b/packages/uniswap/src/features/transactions/swap/hooks/usePermit2Signature.ts index 8196cb0a126..21162addaea 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/usePermit2Signature.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/usePermit2Signature.ts @@ -24,7 +24,7 @@ export function usePermit2SignatureWithData({ permitData, skip }: { permitData: const permitSignatureFetcher = useCallback(async () => { if (skip || !signer || !domain || !types || !values) { - return + return undefined } return await signTypedData( diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSetTradeSlippage.ts b/packages/uniswap/src/features/transactions/swap/hooks/useSetTradeSlippage.ts index 0f9bfd2956d..988023a3603 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSetTradeSlippage.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSetTradeSlippage.ts @@ -5,6 +5,7 @@ import { DynamicConfigs, SwapConfigKey } from 'uniswap/src/features/gating/confi import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { Trade, TradeWithStatus } from 'uniswap/src/features/transactions/swap/types/trade' +import { isBridge } from 'uniswap/src/features/transactions/swap/utils/routing' import { getClassicQuoteFromResponse, transformTradingApiResponseToTrade, @@ -18,6 +19,10 @@ export function useSetTradeSlippage( const autoSlippageTolerance = useCalculateAutoSlippage(trade?.trade) return useMemo(() => { + if (trade.trade && isBridge(trade.trade)) { + // Bridge trades don't have slippage + return { trade, autoSlippageTolerance: 0 } + } // If the user has set a custom slippage, use that in the trade instead of the auto-slippage if (!trade.trade || userSetSlippage) { return { trade, autoSlippageTolerance } diff --git a/packages/wallet/src/features/transactions/swap/hooks/useSwapPrefilledState.ts b/packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts similarity index 98% rename from packages/wallet/src/features/transactions/swap/hooks/useSwapPrefilledState.ts rename to packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts index 6680444bf87..b90cbd3368d 100644 --- a/packages/wallet/src/features/transactions/swap/hooks/useSwapPrefilledState.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts @@ -16,6 +16,7 @@ export function useSwapPrefilledState(initialState: TransactionState | undefined exactAmountFiat: initialState.exactAmountFiat, exactAmountToken: initialState.exactAmountToken, exactCurrencyField: initialState.exactCurrencyField, + filteredChainIds: {}, focusOnCurrencyField: getFocusOnCurrencyFieldFromInitialState(initialState), input: initialState.input ?? undefined, output: initialState.output ?? undefined, diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.test.ts b/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.test.ts index 6e559cfafa3..7dd16a34b7e 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.test.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.test.ts @@ -1,6 +1,7 @@ import { renderHook } from '@testing-library/react-hooks' import { UNI, WBTC } from 'uniswap/src/constants/tokens' -import { FeeType, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { FeeType } from 'uniswap/src/data/tradingApi/types' import { AccountType, SignerMnemonicAccountMeta } from 'uniswap/src/features/accounts/types' import { DEFAULT_GAS_STRATEGY } from 'uniswap/src/features/gas/hooks' import { useSwapTxAndGasInfo } from 'uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo' @@ -64,14 +65,16 @@ describe('useSwapTxAndGasInfo', () => { const mockSwapTxInfo: TransactionRequestInfo = { transactionRequest: { to: '0x456', chainId: 1 }, gasFeeResult: { value: '123', isLoading: false, error: null }, - gasEstimates: { - activeEstimate: { - gasLimit: '500000', - gasFee: '600000', - maxFeePerGas: '700000', - maxPriorityFeePerGas: '800000', - type: FeeType.EIP1559, - strategy: DEFAULT_GAS_STRATEGY, + gasEstimate: { + swapEstimates: { + activeEstimate: { + gasLimit: '500000', + gasFee: '600000', + maxFeePerGas: '700000', + maxPriorityFeePerGas: '800000', + type: FeeType.EIP1559, + strategy: DEFAULT_GAS_STRATEGY, + }, }, }, permitSignature: undefined, @@ -116,10 +119,9 @@ describe('useSwapTxAndGasInfo', () => { }, approvalError: false, indicativeTrade: undefined, - permitSignature: undefined, - permitData: undefined, - permitDataLoading: undefined, + permit: undefined, swapRequestArgs: undefined, + unsigned: false, }) }) }) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.ts index 41191a9a968..c33571fc8e2 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSwapTxAndGasInfo.ts @@ -13,8 +13,9 @@ import { import { ApprovalAction, Trade } from 'uniswap/src/features/transactions/swap/types/trade' import { sumGasFees } from 'uniswap/src/features/transactions/swap/utils/gas' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' -import { validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' +import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' import { CurrencyField } from 'uniswap/src/types/currency' +import { isInterface } from 'utilities/src/platform' export function useSwapTxAndGasInfo({ derivedSwapInfo, @@ -35,6 +36,7 @@ export function useSwapTxAndGasInfo({ chainId, wrapType, currencyInAmount: currencyAmounts[CurrencyField.INPUT], + currencyOutAmount: currencyAmounts[CurrencyField.OUTPUT], routing: trade?.routing, }) @@ -51,7 +53,7 @@ export function useSwapTxAndGasInfo({ const approvalError = tokenApprovalInfo?.action === ApprovalAction.Unknown const gasFeeEstimation: SwapGasFeeEstimation = { - swapEstimates: swapTxInfo.gasEstimates, + ...swapTxInfo.gasEstimate, approvalEstimates: tokenApprovalInfo?.gasEstimates, } @@ -60,6 +62,8 @@ export function useSwapTxAndGasInfo({ const approveTxRequest = validateTransactionRequest(tokenApprovalInfo?.txRequest) const revocationTxRequest = validateTransactionRequest(tokenApprovalInfo?.cancelTxRequest) const txRequest = validateTransactionRequest(swapTxInfo.transactionRequest) + const permit = validatePermit(swapTxInfo.permitData) + const unsigned = Boolean(isInterface && swapTxInfo.permitData) if (trade?.routing === Routing.DUTCH_V2) { const signature = swapTxInfo.permitSignature @@ -80,12 +84,25 @@ export function useSwapTxAndGasInfo({ revocationTxRequest, orderParams, gasFee, + gasFeeEstimation, gasFeeBreakdown, approvalError, - permitData: swapTxInfo.permitData, - permitDataLoading: swapTxInfo.permitDataLoading, + permit, + } + } else if (trade?.routing === Routing.BRIDGE) { + return { + routing: Routing.BRIDGE, + trade, + indicativeTrade: undefined, // Bridge trades don't have indicative trades + txRequest, + approveTxRequest, + revocationTxRequest, + gasFee, + gasFeeEstimation, + approvalError, swapRequestArgs: swapTxInfo.swapRequestArgs, - permitSignature: swapTxInfo.permitSignature, + permit, + unsigned, } } else { return { @@ -98,20 +115,18 @@ export function useSwapTxAndGasInfo({ gasFee, gasFeeEstimation, approvalError, - permitData: swapTxInfo.permitData, - permitDataLoading: swapTxInfo.permitDataLoading, swapRequestArgs: swapTxInfo.swapRequestArgs, - permitSignature: swapTxInfo.permitSignature, + permit, + unsigned, } } }, [ indicativeTrade, - swapTxInfo.gasEstimates, + swapTxInfo.gasEstimate, swapTxInfo.gasFeeResult, swapTxInfo.permitSignature, swapTxInfo.transactionRequest, swapTxInfo.permitData, - swapTxInfo.permitDataLoading, swapTxInfo.swapRequestArgs, tokenApprovalInfo, trade, diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.test.ts b/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.test.ts index 8b7cb9a2d25..7b1dd33ed60 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.test.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.test.ts @@ -1,6 +1,7 @@ import { CurrencyAmount } from '@uniswap/sdk-core' import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types' import { DAI, USDC } from 'uniswap/src/constants/tokens' +import { Locale } from 'uniswap/src/features/language/constants' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { getSwapWarnings } from 'uniswap/src/features/transactions/swap/hooks/useSwapWarnings' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' @@ -111,7 +112,7 @@ const tradeErrorState: DerivedSwapInfo = { gasEstimates: createGasFeeEstimates(), }, } -const { formatPercent } = mockLocalizedFormatter +const { formatPercent } = mockLocalizedFormatter(Locale.EnglishUnitedStates) describe(getSwapWarnings, () => { it('catches incomplete form errors', async () => { diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.test.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.test.ts index b957603e8da..d8c9635c59b 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.test.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.test.ts @@ -1,8 +1,9 @@ import { renderHook } from '@testing-library/react-hooks' import { CurrencyAmount, Token } from '@uniswap/sdk-core' -import { DAI } from 'uniswap/src/constants/tokens' +import { DAI, USDC } from 'uniswap/src/constants/tokens' import { useCheckApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckApprovalQuery' -import { FeeType, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { FeeType } from 'uniswap/src/data/tradingApi/types' import { AccountMeta, AccountType } from 'uniswap/src/features/accounts/types' import { DEFAULT_GAS_STRATEGY } from 'uniswap/src/features/gas/hooks' import { @@ -27,13 +28,16 @@ describe('useTokenApprovalInfo', () => { const mockAccount: AccountMeta = { address: '0x123', type: AccountType.SignerMnemonic } const mockTokenIn = new Token(UniverseChainId.Mainnet, DAI.address, DAI.decimals, DAI.symbol, DAI.name) + const mockTokenOut = new Token(UniverseChainId.Mainnet, USDC.address, USDC.decimals, USDC.symbol, USDC.name) const mockCurrencyInAmount = CurrencyAmount.fromRawAmount(mockTokenIn, '1000000000000000000') // 1 TKIN + const mockCurrencyOutAmount = CurrencyAmount.fromRawAmount(mockTokenOut, '2000000000000000000') // 2 TKOUT const mockParams: TokenApprovalInfoParams = { chainId: UniverseChainId.Mainnet, wrapType: WrapType.NotApplicable, currencyInAmount: mockCurrencyInAmount, + currencyOutAmount: mockCurrencyOutAmount, routing: Routing.CLASSIC, account: mockAccount, skip: false, diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts index eb3b1842d28..d5361b60062 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts @@ -20,6 +20,7 @@ export interface TokenApprovalInfoParams { chainId: WalletChainId wrapType: WrapType currencyInAmount: Maybe> + currencyOutAmount?: Maybe> routing: Routing | undefined account?: AccountMeta skip?: boolean @@ -34,7 +35,7 @@ interface TokenApprovalGasInfo { export function useTokenApprovalInfo( params: TokenApprovalInfoParams, ): (TokenApprovalInfo & TokenApprovalGasInfo) | undefined { - const { account, chainId, wrapType, currencyInAmount, routing, skip } = params + const { account, chainId, wrapType, currencyInAmount, currencyOutAmount, routing, skip } = params const isWrap = wrapType !== WrapType.NotApplicable @@ -43,22 +44,34 @@ export function useTokenApprovalInfo( const currencyIn = routing === Routing.DUTCH_V2 ? currencyInAmount?.currency.wrapped : currencyInAmount?.currency const amount = currencyInAmount?.quotient.toString() - const tokenAddress = getTokenAddressForApi(currencyIn) + const tokenInAddress = getTokenAddressForApi(currencyIn) + + // Only used for bridging + const isBridge = routing === Routing.BRIDGE + const currencyOut = currencyOutAmount?.currency + const tokenOutAddress = getTokenAddressForApi(currencyOut) const approvalRequestArgs: ApprovalRequest | undefined = useMemo(() => { - const supportedChainId = toTradingApiSupportedChainId(chainId) + const tokenInChainId = toTradingApiSupportedChainId(chainId) + const tokenOutChainId = toTradingApiSupportedChainId(currencyOut?.chainId) - if (!address || !amount || !currencyIn || !tokenAddress || !supportedChainId) { + if (!address || !amount || !currencyIn || !tokenInAddress || !tokenInChainId) { + return undefined + } + if (isBridge && !tokenOutAddress && !tokenOutChainId) { return undefined } + return { walletAddress: address, - token: tokenAddress, + token: tokenInAddress, amount, - chainId: supportedChainId, + chainId: tokenInChainId, includeGasInfo: true, + tokenOut: tokenOutAddress, + tokenOutChainId, } - }, [address, amount, chainId, currencyIn, tokenAddress]) + }, [address, amount, chainId, currencyIn, currencyOut?.chainId, isBridge, tokenInAddress, tokenOutAddress]) const shouldSkip = skip || !approvalRequestArgs || isWrap || !address const activeGasStrategy = useActiveGasStrategy(chainId, 'general') diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts index 065d0f718be..8a2c18f2a00 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts @@ -73,7 +73,14 @@ export function useTrade({ const activeGasStrategy = useActiveGasStrategy(tokenInChainId, 'swap') const shadowGasStrategies = useShadowGasStrategies(tokenInChainId, 'swap') - const routingPreference = getRoutingPreferenceForSwapRequest(tradeProtocolPreference, uniswapXEnabled, isUSDQuote) + const isBridging = currencyIn?.chainId !== currencyOut?.chainId + + const routingPreference = getRoutingPreferenceForSwapRequest( + tradeProtocolPreference, + uniswapXEnabled, + isBridging, + isUSDQuote, + ) const requestTradeType = tradeType === TradeType.EXACT_INPUT ? TradingApiTradeType.EXACT_INPUT : TradingApiTradeType.EXACT_OUTPUT diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts new file mode 100644 index 00000000000..2e647b4a06b --- /dev/null +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts @@ -0,0 +1,123 @@ +import { renderHook } from '@testing-library/react-hooks' +import { providers } from 'ethers/lib/ethers' +import { useTradingApiSwapQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery' +import { AccountMeta, AccountType } from 'uniswap/src/features/accounts/types' +import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks' +import { GasFeeResult } from 'uniswap/src/features/gas/types' +import { usePermit2SignatureWithData } from 'uniswap/src/features/transactions/swap/hooks/usePermit2Signature' +import { useTransactionRequestInfo } from 'uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo' +import { useWrapTransactionRequest } from 'uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest' +import { WrapType } from 'uniswap/src/features/transactions/types/wrap' +import { ETH, WETH } from 'uniswap/src/test/fixtures' +import { createMockDerivedSwapInfo, createMockTokenApprovalInfo } from 'uniswap/src/test/fixtures/transactions/swap' +import { UniverseChainId } from 'uniswap/src/types/chains' + +jest.mock('uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery') +jest.mock('uniswap/src/features/transactions/swap/hooks/usePermit2Signature') +jest.mock('uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest') +jest.mock('uniswap/src/features/gas/hooks') +jest.mock('uniswap/src/features/gating/hooks', () => { + return { + useDynamicConfigValue: jest.fn().mockImplementation((config: unknown, key: unknown, defaultVal: unknown) => { + return defaultVal + }), + } +}) + +const mockUseTradingApiSwapQuery = useTradingApiSwapQuery as jest.Mock +const mockUsePermit2SignatureWithData = usePermit2SignatureWithData as jest.Mock +const mockUseWrapTransactionRequest = useWrapTransactionRequest as jest.Mock +const mockUseTransactionGasFee = useTransactionGasFee as jest.Mock + +describe('useTransactionRequestInfo', () => { + const mockAccount: AccountMeta = { address: '0x123', type: AccountType.SignerMnemonic } + const mockWrapGasFee: GasFeeResult = { + value: '250000', + params: { + gasLimit: '250000', + maxFeePerGas: '300000', + maxPriorityFeePerGas: '350000', + }, + isLoading: false, + error: null, + } + const swapQueryResult = { + data: { + requestId: '123', + swap: { + from: '0x123', + data: '0x', + value: '0', + to: '0xSwap', + chainId: UniverseChainId.Mainnet, + gasLimit: '500000', + maxFeePerGas: '600000', + maxPriorityFeePerGas: '700000', + }, + }, + error: null, + isLoading: false, + } + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should include gas fee values from wrapGasFee in the returned wrap transactionRequest', () => { + // Swap needs wrapping + const mockDerivedSwapInfo = createMockDerivedSwapInfo(ETH, WETH, '1000000000000000000', '1000000000', { + wrapType: WrapType.Wrap, + }) + mockUseWrapTransactionRequest.mockReturnValue({ + to: '0xWrap', + chainId: UniverseChainId.Mainnet, + }) + mockUsePermit2SignatureWithData.mockReturnValue({ signature: undefined, isLoading: false }) + mockUseTradingApiSwapQuery.mockReturnValue(swapQueryResult) + mockUseTransactionGasFee.mockReturnValue(mockWrapGasFee) + + const { result } = renderHook(() => + useTransactionRequestInfo({ + derivedSwapInfo: mockDerivedSwapInfo, + tokenApprovalInfo: createMockTokenApprovalInfo(), + account: mockAccount, + skip: false, + }), + ) + + expect(result.current.transactionRequest).toMatchObject({ + to: '0xWrap', + chainId: UniverseChainId.Mainnet, + gasLimit: '250000', + maxFeePerGas: '300000', + maxPriorityFeePerGas: '350000', + }) + }) + + it('should return the swap transactionRequest when wrap is not applicable', () => { + // Swap does not need wrapping + const mockDerivedSwapInfo = createMockDerivedSwapInfo(ETH, WETH, '1000000000000000000', '1000000000') + + mockUseWrapTransactionRequest.mockReturnValue(null) + mockUsePermit2SignatureWithData.mockReturnValue({ signature: undefined, isLoading: false }) + mockUseTradingApiSwapQuery.mockReturnValue(swapQueryResult) + mockUseTransactionGasFee.mockReturnValue({ error: null, isLoading: false }) + + const { result } = renderHook(() => + useTransactionRequestInfo({ + derivedSwapInfo: mockDerivedSwapInfo, + tokenApprovalInfo: createMockTokenApprovalInfo(), + account: mockAccount, + skip: false, + }), + ) + + expect(result.current.transactionRequest).toMatchObject({ + to: '0xSwap', + chainId: UniverseChainId.Mainnet, + gasLimit: '500000', + maxFeePerGas: '600000', + maxPriorityFeePerGas: '700000', + }) + }) +}) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts index 9990a06cfbf..124e4670be7 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts @@ -5,6 +5,7 @@ import { useTradingApiSwapQuery } from 'uniswap/src/data/apiClients/tradingApi/u import { CreateSwapRequest, NullablePermit, + Routing, TransactionFailureReason, } from 'uniswap/src/data/tradingApi/__generated__/index' import { AccountMeta } from 'uniswap/src/features/accounts/types' @@ -18,14 +19,19 @@ import { getBaseTradeAnalyticsPropertiesFromSwapInfo } from 'uniswap/src/feature import { usePermit2SignatureWithData } from 'uniswap/src/features/transactions/swap/hooks/usePermit2Signature' import { useWrapTransactionRequest } from 'uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' +import { SwapGasFeeEstimation } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { ApprovalAction, TokenApprovalInfo } from 'uniswap/src/features/transactions/swap/types/trade' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' -import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi' +import { + getBridgeQuoteFromResponse, + getClassicQuoteFromResponse, + isClassicQuote, +} from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { GasFeeEstimates } from 'uniswap/src/features/transactions/types/transactionDetails' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' import { isDetoxBuild } from 'utilities/src/environment/constants' import { logger } from 'utilities/src/logger/logger' -import { isMobileApp } from 'utilities/src/platform' +import { isInterface, isMobileApp } from 'utilities/src/platform' import { ONE_SECOND_MS } from 'utilities/src/time/time' export const UNKNOWN_SIM_ERROR = 'Unknown gas simulation error' @@ -37,7 +43,7 @@ export interface TransactionRequestInfo { permitData?: NullablePermit permitDataLoading?: boolean gasFeeResult: GasFeeResult - gasEstimates?: GasFeeEstimates + gasEstimate: SwapGasFeeEstimation swapRequestArgs: CreateSwapRequest | undefined } @@ -59,37 +65,40 @@ export function useTransactionRequestInfo({ const { trade: tradeWithStatus, customDeadline } = derivedSwapInfo const { trade } = tradeWithStatus || { trade: undefined } + const isBridgeTrade = trade?.routing === Routing.BRIDGE const permitData = trade?.quote?.permitData - const swapQuote = getClassicQuoteFromResponse(trade?.quote) + // checks within functions for type of trade + const swapQuote = getClassicQuoteFromResponse(trade?.quote) ?? getBridgeQuoteFromResponse(trade?.quote) // Quote indicates we need to include a signed permit message const requiresPermit2Sig = !!permitData - const signatureInfo = usePermit2SignatureWithData({ permitData, skip }) + // On interface, we do not fetch signature until after swap is clicked, as it requires user interaction. + const signatureInfo = usePermit2SignatureWithData({ permitData, skip: skip || isInterface }) /** - * Simulate transactions to ensure they will not fail on-chain. Do not simulate for txs that need an approval as those require Tenderly to simulate and it is not currently integrated into the gas service + * Simulate transactions to ensure they will not fail on-chain. + * Do not simulate for bridge transactions or txs that need an approval + * as those require Tenderly to simulate and it is not currently integrated into the gas servic */ - const shouldSimulateTxn = tokenApprovalInfo?.action === ApprovalAction.None + const shouldSimulateTxn = isBridgeTrade ? false : tokenApprovalInfo?.action === ApprovalAction.None + const missingSig = requiresPermit2Sig && !signatureInfo.signature // Format request args const swapRequestArgs: CreateSwapRequest | undefined = useMemo(() => { - if (requiresPermit2Sig && !signatureInfo.signature) { - return undefined - } // TODO: MOB(2438) https://linear.app/uniswap/issue/MOB-2438/uniswap-x-clean-old-trading-api-code if (!swapQuote) { return undefined } // We cant get correct calldata from /swap if we dont have a valid slippage tolerance - if (tradeWithStatus.trade?.slippageTolerance === undefined) { + if (tradeWithStatus.trade?.slippageTolerance === undefined && !isBridgeTrade) { return undefined } - // TODO: remove this when api does slippage calculation for us + // TODO: update this when api does slippage calculation for us // https://linear.app/uniswap/issue/MOB-2581/remove-slippage-adjustment-in-swap-request - const quoteWithSlippage = { + const quote = { ...swapQuote, - slippage: tradeWithStatus.trade.slippageTolerance, + slippage: tradeWithStatus.trade?.slippageTolerance, } // if custom deadline is set (in minutes), convert to unix timestamp format from now @@ -97,7 +106,7 @@ export function useTransactionRequestInfo({ const deadline = customDeadline ? Math.floor(Date.now() / 1000) + deadlineSeconds : undefined const swapArgs: CreateSwapRequest = { - quote: quoteWithSlippage, + quote, permitData: permitData ?? undefined, signature: signatureInfo.signature, simulateTransaction: shouldSimulateTxn, @@ -110,13 +119,13 @@ export function useTransactionRequestInfo({ }, [ activeGasStrategy, customDeadline, + isBridgeTrade, permitData, - requiresPermit2Sig, shadowGasStrategies, shouldSimulateTxn, signatureInfo.signature, swapQuote, - tradeWithStatus.trade?.slippageTolerance, + tradeWithStatus, ]) // Wrap transaction request @@ -129,12 +138,17 @@ export function useTransactionRequestInfo({ wrapGasFeeRef.current = currentWrapGasFee } // Wrap gas cost should not change significantly between trades, so we can use the last value if current is unavailable. - const wrapGasFee = useMemo( + const wrapGasFee: GasFeeResult = useMemo( () => ({ ...currentWrapGasFee, value: currentWrapGasFee.value ?? wrapGasFeeRef.current.value }), [currentWrapGasFee], ) - const skipTransactionRequest = !swapRequestArgs || isWrapApplicable || skip + const wrapTxRequestWithGasFee = useMemo( + () => ({ ...wrapTxRequest, ...(wrapGasFee.params ?? {}) }), + [wrapTxRequest, wrapGasFee], + ) + + const skipTransactionRequest = !swapRequestArgs || isWrapApplicable || skip || missingSig const tradingApiSwapRequestMs = useDynamicConfigValue( DynamicConfigs.Swap, @@ -159,7 +173,8 @@ export function useTransactionRequestInfo({ const swapGasFee = swapQuote?.gasFee // This is a case where simulation fails on backend, meaning txn is expected to fail - const simulationError = swapQuote?.txFailureReasons?.includes(TransactionFailureReason.SIMULATION_ERROR) + const simulationError = + isClassicQuote(swapQuote) && swapQuote?.txFailureReasons?.includes(TransactionFailureReason.SIMULATION_ERROR) const gasEstimateError = useMemo( () => (simulationError ? new Error(UNKNOWN_SIM_ERROR) : error), [simulationError, error], @@ -210,23 +225,27 @@ export function useTransactionRequestInfo({ } }, [data?.swap, derivedSwapInfo, formatter, gasEstimateError, swapRequestArgs, trade]) - const gasEstimates = useMemo(() => { + const gasEstimate: SwapGasFeeEstimation = useMemo(() => { const activeGasEstimate = data?.gasEstimates?.find((e) => areEqualGasStrategies(e.strategy, activeGasStrategy)) - return activeGasEstimate + const swapGasEstimate: GasFeeEstimates | undefined = activeGasEstimate ? { activeEstimate: activeGasEstimate, shadowEstimates: data?.gasEstimates?.filter((e) => e !== activeGasEstimate), } : undefined - }, [data?.gasEstimates, activeGasStrategy]) + return { + swapEstimates: swapGasEstimate, + wrapEstimates: wrapGasFee.gasEstimates, + } + }, [data?.gasEstimates, activeGasStrategy, wrapGasFee.gasEstimates]) return { gasFeeResult, - transactionRequest: isWrapApplicable ? wrapTxRequest : data?.swap, + transactionRequest: isWrapApplicable ? wrapTxRequestWithGasFee : data?.swap, permitSignature: signatureInfo.signature, permitDataLoading: signatureInfo.isLoading, permitData, - gasEstimates, + gasEstimate, swapRequestArgs, } } diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts b/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts index 2ec119f9cc7..a1eaad6e4ee 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts @@ -63,7 +63,7 @@ export function useUSDCPrice(currency?: Currency): Price | u return useMemo(() => { if (!stablecoin) { - return + return undefined } if (currencyIsStablecoin) { @@ -72,7 +72,7 @@ export function useUSDCPrice(currency?: Currency): Price | u } if (!trade || !isClassic(trade) || !trade.routes?.[0] || !quoteAmount || !currency) { - return + return undefined } const { numerator, denominator } = trade.routes[0].midPrice diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts b/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts index ca9ee2dbab1..00078485f3e 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts @@ -42,7 +42,7 @@ const getWrapTransactionRequest = async ( currencyAmountIn: Maybe>, ): Promise => { if (!address || !currencyAmountIn || !provider || (wrapType === WrapType.NotApplicable && !isUniswapXWrap)) { - return + return undefined } const wethContract = await getWethContract(chainId, provider) diff --git a/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx b/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx new file mode 100644 index 00000000000..ae394e219ae --- /dev/null +++ b/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx @@ -0,0 +1,77 @@ +import { useTranslation } from 'react-i18next' +import { Flex, Text, UniversalImage } from 'ui/src' +import { ACROSS_LOGO } from 'ui/src/assets' +import { InfoCircle } from 'ui/src/components/icons/InfoCircle' +import { iconSizes } from 'ui/src/theme' +import { WarningInfo } from 'uniswap/src/components/modals/WarningModal/WarningInfo' +import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' +import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { isMobileApp } from 'utilities/src/platform' + +export function AcrossRoutingInfo(): JSX.Element { + const { t } = useTranslation() + + return ( + + + + {t('swap.details.orderRouting')} + + + + + + + Across API + + + + } + infoButton={ + isMobileApp ? ( + + ) : undefined + } + modalProps={{ + caption: t('swap.details.orderRoutingInfo'), + rejectText: t('common.button.close'), + modalName: ModalName.AcrossRoutingInfo, + severity: WarningSeverity.None, + title: t('swap.details.orderRouting'), + icon: ( + + ), + }} + tooltipProps={{ + text: ( + + {t('swap.details.orderRoutingInfo')} + + ), + placement: 'top', + }} + trigger={null} + /> + ) +} diff --git a/packages/uniswap/src/features/transactions/swap/modals/FeeOnTransferWarning.tsx b/packages/uniswap/src/features/transactions/swap/modals/FeeOnTransferWarning.tsx index c40e991e7e5..d8dec731890 100644 --- a/packages/uniswap/src/features/transactions/swap/modals/FeeOnTransferWarning.tsx +++ b/packages/uniswap/src/features/transactions/swap/modals/FeeOnTransferWarning.tsx @@ -26,7 +26,7 @@ export function FeeOnTransferWarning({ children }: PropsWithChildren): JSX.Eleme modalProps={{ backgroundIconColor: colors.DEP_magentaDark.val, caption, - closeText: t('common.button.close'), + rejectText: t('common.button.close'), icon: ( ), - closeText: t('common.button.close'), + rejectText: t('common.button.close'), icon: , modalName: ModalName.NetworkFeeInfo, severity: WarningSeverity.None, diff --git a/packages/uniswap/src/features/transactions/swap/modals/PriceImpactWarning.tsx b/packages/uniswap/src/features/transactions/swap/modals/PriceImpactWarning.tsx index 29f28c071b3..5d7bc981431 100644 --- a/packages/uniswap/src/features/transactions/swap/modals/PriceImpactWarning.tsx +++ b/packages/uniswap/src/features/transactions/swap/modals/PriceImpactWarning.tsx @@ -14,7 +14,7 @@ export function PriceImpactWarning({ children, warning }: PropsWithChildren<{ wa } isOpen={isOpen} modalName={ModalName.SwapWarning} severity={warning.severity} title={warning.title ?? ''} onClose={onClose} - onConfirm={onClose} + onAcknowledge={onClose} /> ) } diff --git a/packages/uniswap/src/features/transactions/swap/modals/UniswapXInfo.tsx b/packages/uniswap/src/features/transactions/swap/modals/UniswapXInfo.tsx index e2abd512d6a..f78242aedfc 100644 --- a/packages/uniswap/src/features/transactions/swap/modals/UniswapXInfo.tsx +++ b/packages/uniswap/src/features/transactions/swap/modals/UniswapXInfo.tsx @@ -31,7 +31,7 @@ export function UniswapXInfo({ modalProps={{ backgroundIconColor: opacify(16, colors.uniswapXPurple), caption: t('uniswapx.description'), - closeText: t('common.button.close'), + rejectText: t('common.button.close'), icon: , modalName: ModalName.UniswapXInfo, severity: WarningSeverity.None, diff --git a/packages/uniswap/src/features/transactions/swap/review/EstimatedTime.tsx b/packages/uniswap/src/features/transactions/swap/review/EstimatedTime.tsx new file mode 100644 index 00000000000..7525d03e82c --- /dev/null +++ b/packages/uniswap/src/features/transactions/swap/review/EstimatedTime.tsx @@ -0,0 +1,48 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { Flex, Text } from 'ui/src' +import { ONE_MINUTE_MS, ONE_SECOND_MS } from 'utilities/src/time/time' + +interface EstimatedTimeProps { + timeMs?: number + showOnlyIfLong?: boolean +} + +export function EstimatedTime({ timeMs, showOnlyIfLong }: EstimatedTimeProps): JSX.Element | null { + const { t } = useTranslation() + + const estimatedBridgingTime = useMemo(() => { + if (!timeMs) { + return null + } + + const minutes = Math.floor(timeMs / ONE_MINUTE_MS) + const seconds = Math.floor((timeMs % ONE_MINUTE_MS) / ONE_SECOND_MS) + + if (seconds === 0) { + return t('bridging.estimatedTime.minutesOnly', { minutes }) + } else if (minutes === 0) { + return t('bridging.estimatedTime.secondsOnly', { seconds }) + } else { + return t('bridging.estimatedTime.minutesAndSeconds', { + minutes, + seconds, + }) + } + }, [timeMs, t]) + + if (!timeMs || !estimatedBridgingTime || (showOnlyIfLong && timeMs < ONE_MINUTE_MS)) { + return null + } + + return ( + + + {t('swap.bridging.estimatedTime')} + + + {estimatedBridgingTime} + + + ) +} diff --git a/packages/uniswap/src/features/transactions/swap/review/MaxSlippageRow.tsx b/packages/uniswap/src/features/transactions/swap/review/MaxSlippageRow.tsx index fe4ae674b45..8eda35beeda 100644 --- a/packages/uniswap/src/features/transactions/swap/review/MaxSlippageRow.tsx +++ b/packages/uniswap/src/features/transactions/swap/review/MaxSlippageRow.tsx @@ -1,4 +1,4 @@ -import { Currency, TradeType } from '@uniswap/sdk-core' +import { TradeType } from '@uniswap/sdk-core' import { PropsWithChildren } from 'react' import { useTranslation } from 'react-i18next' import { Flex, Text, TouchableArea, isWeb, useSporeColors } from 'ui/src' @@ -13,7 +13,7 @@ import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' -import { IndicativeTrade, Trade } from 'uniswap/src/features/transactions/swap/types/trade' +import { BridgeTrade, IndicativeTrade, TradeWithSlippage } from 'uniswap/src/features/transactions/swap/types/trade' import { slippageToleranceToPercent } from 'uniswap/src/features/transactions/swap/utils/format' import { NumberType } from 'utilities/src/format/types' @@ -39,6 +39,10 @@ export function MaxSlippageRow({ throw new Error('Invalid render of `MaxSlippageInfo` with no `acceptedTrade`') } + if (acceptedTrade instanceof BridgeTrade) { + throw new Error('Invalid render of `MaxSlippageInfo` for bridge trade') + } + // If we don't have a custom slippage tolerance set, we won't have a tolerance to display for an indicative quote, // since only the full quote has a slippage tolerance. In this case, we display a loading state until the full quote is received. const showLoadingState = acceptedTrade.indicative && !acceptedTrade.slippageTolerance @@ -83,7 +87,7 @@ export function MaxSlippageRow({ } type SlippageWarningContentProps = PropsWithChildren<{ - trade: Trade | IndicativeTrade + trade: TradeWithSlippage | IndicativeTrade isCustomSlippage: boolean autoSlippageTolerance?: number }> @@ -182,7 +186,7 @@ export function SlippageWarningContent({ modalProps={{ backgroundIconColor: colors.surface2.get(), captionComponent: captionContent, - closeText: t('common.button.close'), + rejectText: t('common.button.close'), icon: , modalName: ModalName.SlippageInfo, severity: WarningSeverity.None, diff --git a/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx b/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx index 7c52d6ba88f..2bfc8c8f319 100644 --- a/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx +++ b/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx @@ -9,11 +9,13 @@ import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { FeeOnTransferFeeGroupProps } from 'uniswap/src/features/transactions/TransactionDetails/FeeOnTransferFee' import { TransactionDetails } from 'uniswap/src/features/transactions/TransactionDetails/TransactionDetails' +import { EstimatedTime } from 'uniswap/src/features/transactions/swap/review/EstimatedTime' import { MaxSlippageRow } from 'uniswap/src/features/transactions/swap/review/MaxSlippageRow' import { SwapRateRatio } from 'uniswap/src/features/transactions/swap/review/SwapRateRatio' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { getSwapFeeUsdFromDerivedSwapInfo } from 'uniswap/src/features/transactions/swap/utils/getSwapFeeUsd' +import { isBridge } from 'uniswap/src/features/transactions/swap/utils/routing' import { CurrencyField } from 'uniswap/src/types/currency' import { getFormattedCurrencyAmount, getSymbolDisplayText } from 'uniswap/src/utils/currency' import { NumberType } from 'utilities/src/format/types' @@ -49,6 +51,8 @@ export function SwapDetails({ const formatter = useLocalizationContext() const { convertFiatAmountFormatted, formatPercent } = formatter + const isBridgeTrade = derivedSwapInfo.trade.trade && isBridge(derivedSwapInfo.trade.trade) + const trade = derivedSwapInfo.trade.trade ?? derivedSwapInfo.trade.indicativeTrade const acceptedTrade = acceptedDerivedSwapInfo.trade.trade ?? acceptedDerivedSwapInfo.trade.indicativeTrade @@ -77,7 +81,7 @@ export function SwapDetails({ : undefined const feeOnTransferProps: FeeOnTransferFeeGroupProps | undefined = useMemo(() => { - if (acceptedTrade.indicative) { + if (acceptedTrade.indicative || isBridge(acceptedTrade)) { return undefined } @@ -91,13 +95,17 @@ export function SwapDetails({ tokenSymbol: acceptedTrade.outputAmount.currency.symbol ?? 'Token buy', }, } - }, [ - acceptedTrade.inputAmount.currency.symbol, - acceptedTrade.inputTax, - acceptedTrade.outputAmount.currency.symbol, - acceptedTrade.outputTax, - acceptedTrade.indicative, - ]) + }, [acceptedTrade]) + + const estimatedBridgingTime = useMemo(() => { + const tradeQuote = derivedSwapInfo.trade.trade?.quote + + if (!tradeQuote || !isBridge(tradeQuote)) { + return undefined + } + + return tradeQuote.quote.estimatedFillTimeMs + }, [derivedSwapInfo.trade.trade?.quote]) return ( @@ -131,11 +141,14 @@ export function SwapDetails({ - + {isBridgeTrade && } + {!isBridgeTrade && ( + + )} ) } diff --git a/packages/uniswap/src/features/transactions/swap/review/SwapReviewScreen.tsx b/packages/uniswap/src/features/transactions/swap/review/SwapReviewScreen.tsx index 3d9c9556681..0ee9df7262c 100644 --- a/packages/uniswap/src/features/transactions/swap/review/SwapReviewScreen.tsx +++ b/packages/uniswap/src/features/transactions/swap/review/SwapReviewScreen.tsx @@ -5,7 +5,7 @@ import { Button, Flex, SpinningLoader, Text, isWeb, useHapticFeedback, useIsShor import { BackArrow } from 'ui/src/components/icons/BackArrow' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { iconSizes } from 'ui/src/theme' -import ProgressIndicator from 'uniswap/src/components/ConfirmSwapModal/ProgressIndicator' +import { ProgressIndicator } from 'uniswap/src/components/ConfirmSwapModal/ProgressIndicator' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { AccountType } from 'uniswap/src/features/accounts/types' @@ -29,13 +29,14 @@ import { TransactionAmountsReview } from 'uniswap/src/features/transactions/swap import { SwapCallback } from 'uniswap/src/features/transactions/swap/types/swapCallback' import { isValidSwapTxContext } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { WrapCallback } from 'uniswap/src/features/transactions/swap/types/wrapCallback' -import { TransactionStep, generateSwapSteps } from 'uniswap/src/features/transactions/swap/utils/generateSwapSteps' +import { TransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { getActionName, isWrapAction } from 'uniswap/src/features/transactions/swap/utils/wrap' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { CurrencyField } from 'uniswap/src/types/currency' import { createTransactionId } from 'uniswap/src/utils/createTransactionId' +import { isInterface } from 'utilities/src/platform' interface SwapReviewScreenProps { hideContent: boolean @@ -57,7 +58,9 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu const account = useAccountMeta() const { bottomSheetViewStyles, onClose, authTrigger, setScreen } = useTransactionModalContext() - const [currentStep, _setCurrentStep] = useState<{ step: TransactionStep; accepted: boolean } | undefined>() + const [steps, setSteps] = useState([]) + const [currentStep, setCurrentStep] = useState<{ step: TransactionStep; accepted: boolean } | undefined>() + const isShowingInterfaceReviewSteps = Boolean(isInterface && currentStep) const swapTxContext = useSwapTxContext() const { gasFee } = swapTxContext @@ -71,8 +74,18 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu isSubmitting, updateSwapForm, isFiatMode, + resetSwapForm, } = useSwapFormContext() + const onSuccess = useCallback(() => { + // On interface, the swap component stays mounted; after swap we reset the form to avoid showing the previous values. + if (isInterface) { + resetSwapForm() + setScreen(TransactionScreen.Form) + } + onClose() + }, [onClose, resetSwapForm, setScreen]) + const { autoSlippageTolerance, chainId, @@ -87,8 +100,6 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu const isWrap = isWrapAction(wrapType) - const steps = useMemo(() => generateSwapSteps(swapTxContext), [swapTxContext]) - const { blockingWarning, reviewScreenWarning } = useParsedSwapWarnings() const { @@ -111,6 +122,14 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu setScreen(TransactionScreen.Form) }, [ctxExactCurrencyField, focusOnCurrencyField, setScreen, updateSwapForm]) + const onSubmitTransactionFailed = useCallback(() => { + setScreen(TransactionScreen.Review) + + // Create a new txId for the next transaction, as the existing one may be used in state to track the failed submission. + const newTxId = createTransactionId() + updateSwapForm({ isSubmitting: false, txId: newTxId }) + }, [setScreen, updateSwapForm]) + const onWrap = useMemo(() => { const inputCurrencyAmount = currencyAmounts[CurrencyField.INPUT] const txRequest = isUniswapX(swapTxContext) ? undefined : swapTxContext.txRequest @@ -119,17 +138,28 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu } return () => { - wrapCallback({ account, inputCurrencyAmount, onSuccess: onClose, txRequest, txId, wrapType }) + wrapCallback({ + account, + inputCurrencyAmount, + onSuccess, + onFailure: onSubmitTransactionFailed, + txRequest, + txId, + wrapType, + gasEstimates: swapTxContext.gasFeeEstimation.wrapEstimates, + }) } - }, [account, currencyAmounts, isWrap, onClose, swapTxContext, txId, wrapCallback, wrapType]) - - const onSubmitTransactionFailed = useCallback(() => { - setScreen(TransactionScreen.Review) - - // Create a new txId for the next transaction, as the existing one may be used in state to track the failed submission. - const newTxId = createTransactionId() - updateSwapForm({ isSubmitting: false, txId: newTxId }) - }, [setScreen, updateSwapForm]) + }, [ + account, + currencyAmounts, + isWrap, + onSuccess, + onSubmitTransactionFailed, + swapTxContext, + txId, + wrapCallback, + wrapType, + ]) const { onSwap, validSwap } = useMemo(() => { const isValidSwap = isValidSwapTxContext(swapTxContext) @@ -143,11 +173,12 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu currencyInAmountUSD: currencyAmountsUSDValue[CurrencyField.INPUT], currencyOutAmountUSD: currencyAmountsUSDValue[CurrencyField.OUTPUT], isAutoSlippage: !customSlippageTolerance, - onSubmit: onClose, - steps, + onSuccess, onFailure: onSubmitTransactionFailed, txId, isFiatInputMode: isFiatMode, + setCurrentStep, + setSteps, }) }, validSwap: true, @@ -163,8 +194,7 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu currencyAmountsUSDValue, customSlippageTolerance, isFiatMode, - onClose, - steps, + onSuccess, onSubmitTransactionFailed, swapCallback, swapTxContext, @@ -181,7 +211,7 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu isWrap ? onWrap() : onSwap() }, [reviewScreenWarning, showWarningModal, warningAcknowledged, isWrap, onWrap, onSwap]) - const onSubmitTransaction = useCallback(async () => { + const onSwapButtonClick = useCallback(async () => { updateSwapForm({ isSubmitting: true }) await hapticFeedback.success() @@ -261,15 +291,15 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu {reviewScreenWarning?.warning.title && ( )} @@ -282,9 +312,7 @@ export function SwapReviewScreen(props: SwapReviewScreenProps): JSX.Element | nu /> {currentStep ? ( - acceptedTrade ? ( - - ) : null + ) : isWrap ? ( - - - {!isWeb && !showUniswapXSubmittingUI && ( - - diff --git a/packages/wallet/src/components/WalletPreviewCard/WalletPreviewCard.tsx b/packages/wallet/src/components/WalletPreviewCard/WalletPreviewCard.tsx index 56c37dc4689..51dcf85b24b 100644 --- a/packages/wallet/src/components/WalletPreviewCard/WalletPreviewCard.tsx +++ b/packages/wallet/src/components/WalletPreviewCard/WalletPreviewCard.tsx @@ -53,7 +53,7 @@ export default function WalletPreviewCard({ {balanceFormatted} )} - {!hideSelectionCircle && selected && } + {!hideSelectionCircle && selected && } diff --git a/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap b/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap index cbc3e640b17..0a08db473f1 100644 --- a/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap +++ b/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap @@ -205,11 +205,14 @@ exports[`renders wallet preview card 1`] = ` align="xMidYMid" bbHeight={20} bbWidth={20} + fill="none" focusable={false} meetOrSlice={0} minX={0} minY={0} - strokeWidth={8} + stroke="currentColor" + strokeLinecap="round" + strokeWidth={6} style={ [ { @@ -229,32 +232,37 @@ exports[`renders wallet preview card 1`] = ` ] } tintColor="#FC72FF" - vbHeight={16} - vbWidth={16} + vbHeight={48} + vbWidth={48} > - - + - - - + } + x1="18" + x2="38" + y1="33" + y2="14" + /> diff --git a/apps/mobile/src/components/home/introCards/IntroCard.test.tsx b/packages/wallet/src/components/introCards/IntroCard.test.tsx similarity index 74% rename from apps/mobile/src/components/home/introCards/IntroCard.test.tsx rename to packages/wallet/src/components/introCards/IntroCard.test.tsx index 7eadaa00cfe..c2c2ca508fc 100644 --- a/apps/mobile/src/components/home/introCards/IntroCard.test.tsx +++ b/packages/wallet/src/components/introCards/IntroCard.test.tsx @@ -1,6 +1,6 @@ -import { CardType, IntroCard, IntroCardProps } from 'src/components/home/introCards/IntroCard' -import { render, screen } from 'src/test/test-utils' import { Wallet } from 'ui/src/components/icons' +import { CardType, IntroCard, IntroCardProps } from 'wallet/src/components/introCards/IntroCard' +import { render, screen } from 'wallet/src/test/test-utils' describe(IntroCard, () => { it('should render the passed values', () => { diff --git a/apps/mobile/src/components/home/introCards/IntroCard.tsx b/packages/wallet/src/components/introCards/IntroCard.tsx similarity index 70% rename from apps/mobile/src/components/home/introCards/IntroCard.tsx rename to packages/wallet/src/components/introCards/IntroCard.tsx index 4518bc7a390..c97d0429ef7 100644 --- a/apps/mobile/src/components/home/introCards/IntroCard.tsx +++ b/packages/wallet/src/components/introCards/IntroCard.tsx @@ -1,15 +1,8 @@ -import { - Flex, - GeneratedIcon, - IconProps, - Text, - TouchableArea, - ViewProps, - useIsDarkMode, - useShadowPropsShort, -} from 'ui/src' +import { Gesture, GestureDetector } from 'react-native-gesture-handler' +import { Flex, GeneratedIcon, IconProps, Text, ViewProps, useIsDarkMode, useShadowPropsShort } from 'ui/src' import { X } from 'ui/src/components/icons' import { useTranslation } from 'uniswap/src/i18n' +import { isExtension } from 'utilities/src/platform' export enum CardType { Required, @@ -41,6 +34,13 @@ export function IntroCard({ const shadowProps = useShadowPropsShort() const { t } = useTranslation() + const tap = Gesture.Tap() + .enabled(!!onClose) + .runOnJS(true) + .onEnd(() => { + onClose?.() + }) + return ( - + - + {title} {cardType === CardType.Required ? ( @@ -81,16 +81,18 @@ export function IntroCard({ ) : cardType === CardType.Dismissible ? ( - - - + + + + + ) : cardType === CardType.Swipe ? ( {t('onboarding.home.intro.label.swipe')} ) : null} - + {description} diff --git a/apps/mobile/src/components/home/introCards/IntroCardStack.tsx b/packages/wallet/src/components/introCards/IntroCardStack.tsx similarity index 59% rename from apps/mobile/src/components/home/introCards/IntroCardStack.tsx rename to packages/wallet/src/components/introCards/IntroCardStack.tsx index e6ac34046de..c3f4367617b 100644 --- a/apps/mobile/src/components/home/introCards/IntroCardStack.tsx +++ b/packages/wallet/src/components/introCards/IntroCardStack.tsx @@ -1,22 +1,22 @@ -import { IntroCard, IntroCardProps } from 'src/components/home/introCards/IntroCard' import { SwipeableCardStack } from 'ui/src/components/swipeablecards/SwipeableCardStack' +import { isExtension } from 'utilities/src/platform' +import { IntroCard, IntroCardProps } from 'wallet/src/components/introCards/IntroCard' export type IntroCardWrapper = IntroCardProps & { onPress?: () => void } type IntroCardStackProps = { cards: IntroCardWrapper[] - keyExtractor: (card: IntroCardProps) => string onSwiped?: (card: IntroCardProps, index: number) => void } -export const INTRO_CARD_MIN_HEIGHT = 110 +export const INTRO_CARD_MIN_HEIGHT = isExtension ? 84 : 110 -export function IntroCardStack({ cards, keyExtractor, onSwiped }: IntroCardStackProps): JSX.Element { +export function IntroCardStack({ cards, onSwiped }: IntroCardStackProps): JSX.Element { return ( card.title} minCardHeight={INTRO_CARD_MIN_HEIGHT} renderCard={(card) => } onSwiped={onSwiped} diff --git a/packages/wallet/src/components/introCards/useSharedIntroCards.ts b/packages/wallet/src/components/introCards/useSharedIntroCards.ts new file mode 100644 index 00000000000..50d7c74054c --- /dev/null +++ b/packages/wallet/src/components/introCards/useSharedIntroCards.ts @@ -0,0 +1,64 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { Person } from 'ui/src/components/icons' +import { AccountType } from 'uniswap/src/features/accounts/types' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { OnboardingCardLoggingName } from 'uniswap/src/features/telemetry/types' +import { CardType } from 'wallet/src/components/introCards/IntroCard' +import { IntroCardWrapper } from 'wallet/src/components/introCards/IntroCardStack' +import { selectHasSkippedUnitagPrompt } from 'wallet/src/features/behaviorHistory/selectors' +import { UNITAG_SUFFIX_NO_LEADING_DOT } from 'wallet/src/features/unitags/constants' +import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks' +import { useUnitagClaimHandler } from 'wallet/src/features/unitags/useUnitagClaimHandler' +import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' + +export type IntroCardWithLogging = IntroCardWrapper & { + loggingName: OnboardingCardLoggingName +} + +type useSharedIntroCardsProps = { + navigateToUnitagClaim: () => void + navigateToUnitagIntro: () => void +} + +export function useSharedIntroCards({ + navigateToUnitagClaim, + navigateToUnitagIntro, +}: useSharedIntroCardsProps): IntroCardWithLogging[] { + const { t } = useTranslation() + const activeAccount = useActiveAccountWithThrow() + const claimUnitagEnabled = useFeatureFlag(FeatureFlags.ExtensionClaimUnitag) + + const hasSkippedUnitagPrompt = useSelector(selectHasSkippedUnitagPrompt) + const { canClaimUnitag } = useCanActiveAddressClaimUnitag() + const { handleClaim: handleUnitagClaim, handleDismiss: handleUnitagDismiss } = useUnitagClaimHandler({ + analyticsEntryPoint: 'home', + navigateToClaim: navigateToUnitagClaim, + navigateToIntro: navigateToUnitagIntro, + }) + + const shouldPromptUnitag = + activeAccount.type === AccountType.SignerMnemonic && !hasSkippedUnitagPrompt && canClaimUnitag + + return useMemo(() => { + const output: IntroCardWithLogging[] = [] + + if (shouldPromptUnitag && claimUnitagEnabled) { + output.push({ + loggingName: OnboardingCardLoggingName.ClaimUnitag, + Icon: Person, + title: t('onboarding.home.intro.unitag.title', { + unitagDomain: UNITAG_SUFFIX_NO_LEADING_DOT, + }), + description: t('onboarding.home.intro.unitag.description'), + cardType: CardType.Dismissible, + onPress: () => handleUnitagClaim(), + onClose: () => handleUnitagDismiss(), + }) + } + + return output + }, [claimUnitagEnabled, shouldPromptUnitag, handleUnitagClaim, handleUnitagDismiss, t]) +} diff --git a/packages/wallet/src/components/modals/InfoLinkModal.tsx b/packages/wallet/src/components/modals/InfoLinkModal.tsx index 6bf93f92d54..447e0cd5257 100644 --- a/packages/wallet/src/components/modals/InfoLinkModal.tsx +++ b/packages/wallet/src/components/modals/InfoLinkModal.tsx @@ -86,6 +86,9 @@ export function InfoLinkModal({ backgroundColor="transparent" borderRadius="$rounded12" color="$neutral2" + hoverStyle={{ + backgroundColor: 'transparent', + }} px="$spacing40" theme="secondary" onPress={openUniswapURL} diff --git a/packages/wallet/src/components/nfts/NFTHiddenRow.tsx b/packages/wallet/src/components/nfts/NFTHiddenRow.tsx index b6fc2fee137..f9fc5068604 100644 --- a/packages/wallet/src/components/nfts/NFTHiddenRow.tsx +++ b/packages/wallet/src/components/nfts/NFTHiddenRow.tsx @@ -14,8 +14,14 @@ export function HiddenNftsRow({ const { t } = useTranslation() return ( - - + + diff --git a/packages/wallet/src/components/nfts/ShowNFTModal.tsx b/packages/wallet/src/components/nfts/ShowNFTModal.tsx index c35bf170ae5..04d91b4301b 100644 --- a/packages/wallet/src/components/nfts/ShowNFTModal.tsx +++ b/packages/wallet/src/components/nfts/ShowNFTModal.tsx @@ -28,7 +28,7 @@ export function ShowNFTModal(): JSX.Element { return ( <> - + diff --git a/packages/wallet/src/components/text/RelativeChange.test.tsx b/packages/wallet/src/components/text/RelativeChange.test.tsx index 3f7f1477022..28541bd44dd 100644 --- a/packages/wallet/src/components/text/RelativeChange.test.tsx +++ b/packages/wallet/src/components/text/RelativeChange.test.tsx @@ -1,7 +1,6 @@ import renderer from 'react-test-renderer' import { FiatCurrencyInfo } from 'uniswap/src/features/fiatOnRamp/types' import { Locale } from 'uniswap/src/features/language/constants' -import { mockLocalizationContext } from 'uniswap/src/test/mocks' import { TamaguiProvider } from 'wallet/src/providers/tamagui-provider' // Needs to be imported after the mock localization context @@ -32,8 +31,6 @@ jest.mock('uniswap/src/features/fiatCurrency/hooks', () => { } }) -jest.mock('uniswap/src/features/language/LocalizationContext', () => mockLocalizationContext) - it('renders a relative change', () => { const tree = renderer.create( diff --git a/packages/wallet/src/contexts/WalletNavigationContext.tsx b/packages/wallet/src/contexts/WalletNavigationContext.tsx index 58e43300fc1..10f68accb84 100644 --- a/packages/wallet/src/contexts/WalletNavigationContext.tsx +++ b/packages/wallet/src/contexts/WalletNavigationContext.tsx @@ -1,11 +1,11 @@ import { createContext, ReactNode, useContext } from 'react' import { FiatOnRampCurrency } from 'uniswap/src/features/fiatOnRamp/types' +import { getSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' import { WalletChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { NFTItem } from 'wallet/src/features/nfts/types' import { getSendPrefilledState } from 'wallet/src/features/transactions/send/getSendPrefilledState' -import { getSwapPrefilledState } from 'wallet/src/features/transactions/swap/hooks/useSwapPrefilledState' type NavigateToTransactionFlowTransactionState = { initialState: TransactionState diff --git a/packages/wallet/src/features/activity/hooks.ts b/packages/wallet/src/features/activity/hooks.ts index 6b2f1069299..28de10f7218 100644 --- a/packages/wallet/src/features/activity/hooks.ts +++ b/packages/wallet/src/features/activity/hooks.ts @@ -62,7 +62,7 @@ export function useFormattedTransactionDataForFeed( const transactions = useMemo(() => { if (!data) { - return + return undefined } return parseDataResponseToFeedTransactionDetails(data, hideSpamTokens) @@ -90,7 +90,7 @@ export function useFormattedTransactionDataForFeed( } if (!hasTransactions) { - return + return undefined } return [ @@ -169,7 +169,7 @@ export function useFormattedTransactionDataForActivity( const formattedTransactions = useMemo(() => { if (!data) { - return + return undefined } return parseDataResponseToTransactionDetails(data, hideSpamTokens, nftVisibility, tokenVisibilityOverrides) @@ -199,7 +199,7 @@ export function useFormattedTransactionDataForActivity( } if (!hasTransactions) { - return + return undefined } return [ diff --git a/packages/wallet/src/features/auth/saga.ts b/packages/wallet/src/features/auth/saga.ts index 6d6eccea411..312fb4e3b7b 100644 --- a/packages/wallet/src/features/auth/saga.ts +++ b/packages/wallet/src/features/auth/saga.ts @@ -14,6 +14,8 @@ function* auth(params: UnlockParams | LockParams) { } else if (params.type === AuthActionType.Lock) { return yield* call(lock) } + + return undefined } function* unlock({ password }: UnlockParams) { diff --git a/packages/wallet/src/features/behaviorHistory/slice.ts b/packages/wallet/src/features/behaviorHistory/slice.ts index 75ea2cda288..d49f309ac7f 100644 --- a/packages/wallet/src/features/behaviorHistory/slice.ts +++ b/packages/wallet/src/features/behaviorHistory/slice.ts @@ -12,6 +12,7 @@ export interface BehaviorHistoryState { hasUsedExplore: boolean backupReminderLastSeenTs?: number hasViewedOffRampTooltip: boolean + hasDismissedBridgingWarning?: boolean } export const initialBehaviorHistoryState: BehaviorHistoryState = { @@ -22,6 +23,7 @@ export const initialBehaviorHistoryState: BehaviorHistoryState = { hasUsedExplore: false, backupReminderLastSeenTs: undefined, hasViewedOffRampTooltip: false, + hasDismissedBridgingWarning: false, } const slice = createSlice({ @@ -49,6 +51,9 @@ const slice = createSlice({ setHasViewedOffRampTooltip: (state, action: PayloadAction) => { state.hasViewedOffRampTooltip = action.payload }, + setHasDismissedBridgingWarning: (state, action: PayloadAction) => { + state.hasDismissedBridgingWarning = action.payload + }, // Should only be used for testing resetBehaviorHistory: (state, _action: PayloadAction) => { @@ -70,6 +75,7 @@ export const { setHasUsedExplore, setBackupReminderLastSeenTs, setHasViewedOffRampTooltip, + setHasDismissedBridgingWarning, resetBehaviorHistory, } = slice.actions diff --git a/packages/wallet/src/features/gas/adjustGasFee.ts b/packages/wallet/src/features/gas/adjustGasFee.ts index 0731aedea81..ff98d870d0a 100644 --- a/packages/wallet/src/features/gas/adjustGasFee.ts +++ b/packages/wallet/src/features/gas/adjustGasFee.ts @@ -1,5 +1,5 @@ import { BigNumber, BigNumberish, providers } from 'ethers' -import { FeeType } from 'uniswap/src/data/tradingApi/__generated__' +import { FeeType } from 'uniswap/src/data/tradingApi/types' import { GasFeeResult } from 'uniswap/src/features/gas/types' import { BigNumberMax } from 'wallet/src/utils/number' diff --git a/packages/wallet/src/features/gas/hooks.ts b/packages/wallet/src/features/gas/hooks.ts index c7e741d9801..e0c08f77d08 100644 --- a/packages/wallet/src/features/gas/hooks.ts +++ b/packages/wallet/src/features/gas/hooks.ts @@ -1,7 +1,7 @@ import { BigNumber, providers } from 'ethers' import { useCallback, useMemo } from 'react' import { TRANSACTION_CANCELLATION_GAS_FACTOR } from 'uniswap/src/constants/transactions' -import { FeeType } from 'uniswap/src/data/tradingApi/__generated__' +import { FeeType } from 'uniswap/src/data/tradingApi/types' import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { TransactionDetails } from 'uniswap/src/features/transactions/types/transactionDetails' @@ -38,13 +38,13 @@ export function useCancelationGasFeeInfo(transaction: TransactionDetails): Cance return useMemo(() => { if (isUniswapXTx) { if (!uniswapXCancelRequest.data || !uniswapXGasFee.value) { - return + return undefined } return { cancelRequest: uniswapXCancelRequest.data, cancelationGasFee: uniswapXGasFee.value } } if (!baseTxGasFee.params) { - return + return undefined } let adjustedFeeDetails: FeeDetails | undefined @@ -59,7 +59,7 @@ export function useCancelationGasFeeInfo(transaction: TransactionDetails): Cance tags: { file: 'features/gas/hooks.ts', function: 'getAdjustedGasFeeDetails' }, extra: { request: transaction.options.request, currentGasFeeParams: baseTxGasFee.params }, }) - return + return undefined } const cancelRequest = { diff --git a/packages/wallet/src/features/images/ElementAfterText.tsx b/packages/wallet/src/features/images/ElementAfterText.tsx new file mode 100644 index 00000000000..afcedbdc9e2 --- /dev/null +++ b/packages/wallet/src/features/images/ElementAfterText.tsx @@ -0,0 +1,39 @@ +import type { FlexProps, TextProps } from 'ui/src' +import { Flex, Text, isWeb } from 'ui/src' +import { usePostTextElementPositionProps } from 'wallet/src/utils/layout' + +type ElementAfterTextProps = { + image?: JSX.Element + caption: string + wrapperProps?: FlexProps + textProps?: TextProps +} + +const DEFAULT_TEXT_PROPS: TextProps = { + color: '$neutral1', + variant: 'body2', +} + +export function ElementAfterText({ image, caption, wrapperProps, textProps }: ElementAfterTextProps): JSX.Element { + const { postTextElementPositionProps, onTextLayout } = usePostTextElementPositionProps() + + if (isWeb) { + return ( + + + {caption} + {image} + + + ) + } else { + return ( + + + {caption} + + {image} + + ) + } +} diff --git a/packages/wallet/src/features/images/RemoteSvg.tsx b/packages/wallet/src/features/images/RemoteSvg.tsx index 3c3bd8eb6b9..5dfd0ae60eb 100644 --- a/packages/wallet/src/features/images/RemoteSvg.tsx +++ b/packages/wallet/src/features/images/RemoteSvg.tsx @@ -26,7 +26,7 @@ export const RemoteSvg = ({ }: Props): JSX.Element | null => { const fetchSvg = useCallback(async () => { if (!imageHttpUrl) { - return + return undefined } try { const res = await fetch(imageHttpUrl) @@ -38,6 +38,7 @@ export const RemoteSvg = ({ tags: { file: 'RemoteSvg', function: 'fetchSvg' }, extra: { imageHttpUrl }, }) + return undefined } }, [imageHttpUrl]) diff --git a/packages/wallet/src/features/nfts/utils.ts b/packages/wallet/src/features/nfts/utils.ts index 04c7910b74f..24fed51b3ad 100644 --- a/packages/wallet/src/features/nfts/utils.ts +++ b/packages/wallet/src/features/nfts/utils.ts @@ -5,7 +5,7 @@ import { NFTItem } from 'wallet/src/features/nfts/types' export function formatNftItems(data: NftsTabQuery | undefined): NFTItem[] | undefined { const items = data?.nftBalances?.edges?.flatMap((item) => item.node) if (!items) { - return + return undefined } const nfts = items diff --git a/packages/wallet/src/features/notifications/components/NotificationToast.tsx b/packages/wallet/src/features/notifications/components/NotificationToast.tsx index 4211486d67a..49e34d67387 100644 --- a/packages/wallet/src/features/notifications/components/NotificationToast.tsx +++ b/packages/wallet/src/features/notifications/components/NotificationToast.tsx @@ -11,9 +11,9 @@ import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { borderRadii, spacing } from 'ui/src/theme' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { useTimeout } from 'utilities/src/time/timing' +import { ElementAfterText } from 'wallet/src/features/images/ElementAfterText' import { selectActiveAccountNotifications } from 'wallet/src/features/notifications/selectors' import { popNotification, setNotificationViewed } from 'wallet/src/features/notifications/slice' -import { usePostTextElementPositionProps } from 'wallet/src/utils/layout' const NOTIFICATION_HEIGHT = 64 @@ -205,7 +205,6 @@ function NotificationContent({ onPress, onPressIn, }: NotificationContentProps): JSX.Element { - const { postTextElementPositionProps, onTextLayout } = usePostTextElementPositionProps() return ( {icon} - - - {title} - - {postCaptionElement} - - {subtitle && ( - - {subtitle} - - )} + + {subtitle && ( + + {subtitle} + + )} {actionButton && ( diff --git a/packages/wallet/src/features/notifications/components/SwapPendingNotification.tsx b/packages/wallet/src/features/notifications/components/SwapPendingNotification.tsx index a3e835404fe..29144832548 100644 --- a/packages/wallet/src/features/notifications/components/SwapPendingNotification.tsx +++ b/packages/wallet/src/features/notifications/components/SwapPendingNotification.tsx @@ -25,6 +25,7 @@ export function SwapPendingNotification({ notification }: { notification: SwapPe ) } +// eslint-disable-next-line consistent-return function getNotificationText(wrapType: WrapType, t: TFunction): string { switch (wrapType) { case WrapType.NotApplicable: diff --git a/packages/wallet/src/features/notifications/selectors.ts b/packages/wallet/src/features/notifications/selectors.ts index 491fb7fc28f..a03c6c911e2 100644 --- a/packages/wallet/src/features/notifications/selectors.ts +++ b/packages/wallet/src/features/notifications/selectors.ts @@ -10,7 +10,7 @@ export const selectActiveAccountNotifications = createSelector( selectActiveAccountAddress, (notificationQueue, address) => { if (!address) { - return + return undefined } // If a notification doesn't have an address param assume it belongs to the active account return notificationQueue.filter((notif) => !notif.address || notif.address === address) diff --git a/packages/wallet/src/features/notifications/utils.test.ts b/packages/wallet/src/features/notifications/utils.test.ts index fb564b52ef6..16b71fce1d2 100644 --- a/packages/wallet/src/features/notifications/utils.test.ts +++ b/packages/wallet/src/features/notifications/utils.test.ts @@ -1,14 +1,17 @@ import { TradeType } from '@uniswap/sdk-core' import { DAI, USDC } from 'uniswap/src/constants/tokens' +import { Locale } from 'uniswap/src/features/language/constants' import { TransactionStatus } from 'uniswap/src/features/transactions/types/transactionDetails' import { mockLocalizedFormatter } from 'uniswap/src/test/mocks' import { formSwapNotificationTitle } from 'wallet/src/features/notifications/utils' +const mockFormatter = mockLocalizedFormatter(Locale.EnglishUnitedStates) + describe(formSwapNotificationTitle, () => { it('formats successful local swap title', () => { expect( formSwapNotificationTitle( - mockLocalizedFormatter, + mockFormatter, TransactionStatus.Success, DAI, USDC, @@ -24,7 +27,7 @@ describe(formSwapNotificationTitle, () => { it('formats successful remote swap title', () => { expect( formSwapNotificationTitle( - mockLocalizedFormatter, + mockFormatter, TransactionStatus.Success, DAI, USDC, @@ -39,7 +42,7 @@ describe(formSwapNotificationTitle, () => { it('formats canceled swap title', () => { expect( formSwapNotificationTitle( - mockLocalizedFormatter, + mockFormatter, TransactionStatus.Canceled, DAI, USDC, @@ -55,7 +58,7 @@ describe(formSwapNotificationTitle, () => { it('formats failed swap title', () => { expect( formSwapNotificationTitle( - mockLocalizedFormatter, + mockFormatter, TransactionStatus.Failed, DAI, USDC, diff --git a/packages/wallet/src/features/notifications/utils.ts b/packages/wallet/src/features/notifications/utils.ts index 5921beb3799..98cdd4ec63a 100644 --- a/packages/wallet/src/features/notifications/utils.ts +++ b/packages/wallet/src/features/notifications/utils.ts @@ -11,6 +11,7 @@ import { getCurrencyDisplayText, getFormattedCurrencyAmount, getSymbolDisplayTex import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' import { WalletConnectNotification } from 'wallet/src/features/notifications/types' +// eslint-disable-next-line consistent-return export const formWCNotificationTitle = (appNotification: WalletConnectNotification): string => { const { event, dappName, chainId } = appNotification diff --git a/packages/wallet/src/features/portfolio/AnimatedNumber.tsx b/packages/wallet/src/features/portfolio/AnimatedNumber.tsx index 3f7b07a37ed..68c1f276682 100644 --- a/packages/wallet/src/features/portfolio/AnimatedNumber.tsx +++ b/packages/wallet/src/features/portfolio/AnimatedNumber.tsx @@ -101,7 +101,7 @@ const RollNumber = ({ key={idx} allowFontScaling={false} fontFamily="$heading" - style={[AnimatedFontStyles.fontStyle, { height: DIGIT_HEIGHT, color: currentColor }]} + style={[AnimatedFontStyles.fontStyle, { height: DIGIT_HEIGHT, color: nextColor ?? currentColor }]} > {char} diff --git a/packages/wallet/src/features/portfolio/HiddenTokensRow.tsx b/packages/wallet/src/features/portfolio/HiddenTokensRow.tsx index 97805e93282..958ee23cd30 100644 --- a/packages/wallet/src/features/portfolio/HiddenTokensRow.tsx +++ b/packages/wallet/src/features/portfolio/HiddenTokensRow.tsx @@ -1,10 +1,9 @@ import { useTranslation } from 'react-i18next' import { Flex, ImpactFeedbackStyle, Separator, Text, TouchableArea } from 'ui/src' import { AnglesDownUp, SortVertical } from 'ui/src/components/icons' -import { TestID } from 'uniswap/src/test/fixtures/testIDs' +import { isMobileApp } from 'utilities/src/platform' export function HiddenTokensRow({ - padded = false, numHidden, isExpanded, onPress, @@ -21,30 +20,29 @@ export function HiddenTokensRow({ hapticFeedback activeOpacity={1} hapticStyle={ImpactFeedbackStyle.Light} - mx="$spacing12" + mx={isMobileApp && '$spacing16'} onPress={onPress} > - - - {/* just used for opacity styling, the parent TouchableArea handles event */} - - - + + + + + + {t('hidden.tokens.info.text.button', { numHidden })} - {isExpanded ? ( - - ) : ( - - )} + + + {isExpanded ? ( + + ) : ( + + )} + - - + + + ) diff --git a/packages/wallet/src/features/portfolio/PortfolioBalance.tsx b/packages/wallet/src/features/portfolio/PortfolioBalance.tsx index 2656248b964..8233bca2c08 100644 --- a/packages/wallet/src/features/portfolio/PortfolioBalance.tsx +++ b/packages/wallet/src/features/portfolio/PortfolioBalance.tsx @@ -6,6 +6,8 @@ import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' import { useAppFiatCurrency, useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { NumberType } from 'utilities/src/format/types' +import { isWeb } from 'utilities/src/platform' +import { ONE_SECOND_MS } from 'utilities/src/time/time' import { RelativeChange } from 'wallet/src/components/text/RelativeChange' import { isWarmLoadingStatus } from 'wallet/src/data/utils' import AnimatedNumber from 'wallet/src/features/portfolio/AnimatedNumber' @@ -14,6 +16,10 @@ interface PortfolioBalanceProps { owner: Address } +// This ensures the color changes quicker after animating on web platforms. This could be +// safe for mobile to be the same but was kept the same as this animation is fragile +const BALANCE_CHANGE_INDICATION_DURATION = isWeb ? ONE_SECOND_MS / 2 : ONE_SECOND_MS * 2 + export const PortfolioBalance = memo(function _PortfolioBalance({ owner }: PortfolioBalanceProps): JSX.Element { const { data, loading, networkStatus } = usePortfolioTotalValue({ address: owner, @@ -41,7 +47,7 @@ export const PortfolioBalance = memo(function _PortfolioBalance({ owner }: Portf } else if (isSwapTransactionInfo(typeInfo)) { return + } else if (isBridgeTransactionInfo(typeInfo)) { + return null // TODO: Update when bridge logo is added } else if (isWCConfirmTransactionInfo(typeInfo)) { return } else if (isWrapTransactionInfo(typeInfo)) { diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsModal.tsx b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsModal.tsx index d95eafc9e1f..fe152cc31aa 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsModal.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsModal.tsx @@ -2,7 +2,7 @@ import dayjs from 'dayjs' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Button, ContextMenu, Flex, Separator, Text, TouchableArea, isWeb } from 'ui/src' -import { AnglesDownUp, SortVertical, TripleDots, UniswapX } from 'ui/src/components/icons' +import { AnglesDownUp, Ellipsis, SortVertical, UniswapX } from 'ui/src/components/icons' import { Modal } from 'uniswap/src/components/modals/Modal' import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' import { AssetType } from 'uniswap/src/entities/assets' @@ -80,12 +80,12 @@ export function TransactionDetailsHeader({ {isWeb ? ( - + ) : ( - + )} diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/SwapTransactionDetails.test.tsx.snap b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/SwapTransactionDetails.test.tsx.snap index c0e2db1cefa..24757ec94a3 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/SwapTransactionDetails.test.tsx.snap +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/SwapTransactionDetails.test.tsx.snap @@ -98,7 +98,7 @@ exports[`SwapTransactionDetails Component renders SwapTransactionDetails without "borderWidth": 0, }, { - "color": "#CECECE", + "color": "#BFBFBF", "height": 20, "transform": [ { @@ -114,7 +114,7 @@ exports[`SwapTransactionDetails Component renders SwapTransactionDetails without }, ] } - tintColor="#CECECE" + tintColor="#BFBFBF" vbHeight={24} vbWidth={24} > diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap index 677cb4fa07c..89471cb2b37 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap @@ -271,12 +271,11 @@ exports[`TransactionDetails Components renders TransactionDetailsHeader without align="xMidYMid" bbHeight={20} bbWidth={20} - fill="currentColor" + fill="none" focusable={false} meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" strokeWidth={8} style={ [ @@ -301,27 +300,17 @@ exports[`TransactionDetails Components renders TransactionDetailsHeader without vbWidth={18} > @@ -837,7 +826,7 @@ exports[`TransactionDetails Components renders TransactionDetailsInfoRows withou "borderWidth": 0, }, { - "color": "#CECECE", + "color": "#BFBFBF", "height": 16, "width": 16, }, @@ -848,7 +837,7 @@ exports[`TransactionDetails Components renders TransactionDetailsInfoRows withou }, ] } - tintColor="#CECECE" + tintColor="#BFBFBF" vbHeight={16} vbWidth={16} > diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/types.ts b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/types.ts index 9b7bbcad45f..97dc0e73873 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/types.ts +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/types.ts @@ -1,4 +1,5 @@ import { + BridgeTransactionInfo, ConfirmedSwapTransactionInfo, ExactInputSwapTransactionInfo, ExactOutputSwapTransactionInfo, @@ -66,6 +67,10 @@ export function isSwapTransactionInfo(typeInfo: TransactionTypeInfo): typeInfo i return typeInfo.type === TransactionType.Swap } +export function isBridgeTransactionInfo(typeInfo: TransactionTypeInfo): typeInfo is BridgeTransactionInfo { + return typeInfo.type === TransactionType.Bridge +} + export function isWCConfirmTransactionInfo(typeInfo: TransactionTypeInfo): typeInfo is WCConfirmInfo { return typeInfo.type === TransactionType.WCConfirm } diff --git a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx index 513eb1b496e..2afcda78fa2 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx @@ -9,6 +9,7 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { TransactionStatus } from 'uniswap/src/features/transactions/types/transactionDetails' import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText' +import { ElementAfterText } from 'wallet/src/features/images/ElementAfterText' import { TransactionDetailsModal } from 'wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsModal' import { useTransactionActions } from 'wallet/src/features/transactions/SummaryCards/DetailsModal/useTransactionActions' import { TransactionSummaryTitle } from 'wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryTitle' @@ -21,7 +22,6 @@ import { } from 'wallet/src/features/transactions/SummaryCards/utils' import { useIsQueuedTransaction } from 'wallet/src/features/transactions/hooks' import { useActiveAccountWithThrow, useDisplayName } from 'wallet/src/features/wallet/hooks' -import { usePostTextElementPositionProps } from 'wallet/src/utils/layout' import { openTransactionLink } from 'wallet/src/utils/linking' const LOADING_SPINNER_SIZE = 20 @@ -74,8 +74,6 @@ function TransactionSummaryLayout({ } } - const { postTextElementPositionProps, onTextLayout } = usePostTextElementPositionProps() - const formattedAddedTime = useFormattedTime(transaction.addedTime) const statusIconFill = colors.surface1.get() @@ -138,12 +136,14 @@ function TransactionSummaryLayout({ {!inProgress && rightBlock} - - - {caption} - - {postCaptionElement} - + {status === TransactionStatus.Failed && onRetry && ( diff --git a/packages/wallet/src/features/transactions/SummaryCards/utils.ts b/packages/wallet/src/features/transactions/SummaryCards/utils.ts index 51849869350..3dab9574bc4 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/utils.ts +++ b/packages/wallet/src/features/transactions/SummaryCards/utils.ts @@ -76,6 +76,7 @@ export function generateActivityItemRenderer( case TransactionType.Send: SummaryItem = SendSummaryItem break + case TransactionType.Bridge: // TODO: Add bridge summary item in next PR case TransactionType.Swap: SummaryItem = SwapSummaryItem break diff --git a/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx b/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx index 8f36b1244e1..cdb85ff2484 100644 --- a/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx +++ b/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx @@ -214,12 +214,12 @@ export function getReceiveNotificationFromData( hideSpamTokens = false, ): ReceiveCurrencyTxNotification | ReceiveNFTNotification | undefined { if (!data || !lastTxNotificationUpdateTimestamp) { - return + return undefined } const parsedTxHistory = parseDataResponseToTransactionDetails(data, hideSpamTokens) if (!parsedTxHistory) { - return + return undefined } const latestReceivedTx = parsedTxHistory @@ -232,13 +232,9 @@ export function getReceiveNotificationFromData( tx.status === TransactionStatus.Success, ) - if (!latestReceivedTx) { - return - } - // Suppress notification if rules apply - if (shouldSuppressNotification(latestReceivedTx)) { - return + if (!latestReceivedTx || shouldSuppressNotification(latestReceivedTx)) { + return undefined } return buildReceiveNotification(latestReceivedTx, address) diff --git a/packages/wallet/src/features/transactions/cancelTransactionSaga.ts b/packages/wallet/src/features/transactions/cancelTransactionSaga.ts index 568be67b278..679a06ab783 100644 --- a/packages/wallet/src/features/transactions/cancelTransactionSaga.ts +++ b/packages/wallet/src/features/transactions/cancelTransactionSaga.ts @@ -4,7 +4,7 @@ import { Contract, providers } from 'ethers' import { call, select } from 'typed-redux-saga' import PERMIT2_ABI from 'uniswap/src/abis/permit2.json' import { Permit2 } from 'uniswap/src/abis/types' -import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing' +import { isBridge, isClassic, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { TransactionDetails, UniswapXOrderDetails } from 'uniswap/src/features/transactions/types/transactionDetails' import { getValidAddress } from 'uniswap/src/utils/addresses' import { logger } from 'utilities/src/logger/logger' @@ -21,9 +21,9 @@ export function* attemptCancelTransaction( transaction: TransactionDetails, cancelRequest: providers.TransactionRequest, ) { - if (isClassic(transaction)) { + if (isClassic(transaction) || isBridge(transaction)) { yield* call(attemptReplaceTransaction, transaction, cancelRequest, true) - } else { + } else if (isUniswapX(transaction)) { yield* call(cancelOrder, transaction, cancelRequest) } } @@ -41,7 +41,7 @@ export async function getCancelOrderTxRequest( } else { const { encodedOrder } = (await getOrders([orderHash])).orders[0] ?? {} if (!encodedOrder) { - return + return undefined } const nonce = CosignedV2DutchOrder.parse(encodedOrder, chainId).info.nonce diff --git a/packages/wallet/src/features/transactions/getAmountsFromTrade.ts b/packages/wallet/src/features/transactions/getAmountsFromTrade.ts index b0f7382d7fc..5ac744bbb31 100644 --- a/packages/wallet/src/features/transactions/getAmountsFromTrade.ts +++ b/packages/wallet/src/features/transactions/getAmountsFromTrade.ts @@ -1,15 +1,21 @@ import { TradeType } from '@uniswap/sdk-core' import { + BridgeTransactionInfo, ConfirmedSwapTransactionInfo, ExactInputSwapTransactionInfo, ExactOutputSwapTransactionInfo, + isBridgeTypeInfo, isConfirmedSwapTypeInfo, } from 'uniswap/src/features/transactions/types/transactionDetails' export function getAmountsFromTrade( - typeInfo: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo | ConfirmedSwapTransactionInfo, + typeInfo: + | ExactInputSwapTransactionInfo + | ExactOutputSwapTransactionInfo + | ConfirmedSwapTransactionInfo + | BridgeTransactionInfo, ): { inputCurrencyAmountRaw: string; outputCurrencyAmountRaw: string } { - if (isConfirmedSwapTypeInfo(typeInfo)) { + if (isConfirmedSwapTypeInfo(typeInfo) || isBridgeTypeInfo(typeInfo)) { const { inputCurrencyAmountRaw, outputCurrencyAmountRaw } = typeInfo return { inputCurrencyAmountRaw, outputCurrencyAmountRaw } } diff --git a/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts b/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts index 4b8c1a6ba59..41bfd8c3479 100644 --- a/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts +++ b/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts @@ -96,7 +96,7 @@ export function extractFiatOnRampTransactionDetails( function: 'extractFiatOnRampTransactionDetails', }, }) - return + return undefined } } diff --git a/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts b/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts index 6f19b2e1abe..a6c336be458 100644 --- a/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts +++ b/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts @@ -24,6 +24,7 @@ export function extractUniswapXOrderDetails(transaction: TransactionListQueryRes } const typeInfo = parseUniswapXOrderTransaction(transaction) + // TODO: does not support parsing priority orders yet since priority orders are not supported in the trading API const routing = transaction.details.swapOrderType === SwapOrderType.Limit ? Routing.DUTCH_LIMIT : Routing.DUTCH_V2 // TODO (MOB-3609): Parse and show pending limit orders in Activity feed @@ -44,6 +45,7 @@ export function extractUniswapXOrderDetails(transaction: TransactionListQueryRes } } +// eslint-disable-next-line consistent-return function remoteOrderStatusToLocalTxStatus(orderStatus: SwapOrderStatus): TransactionStatus { switch (orderStatus) { case SwapOrderStatus.Open: diff --git a/packages/wallet/src/features/transactions/history/conversion/parseTradeTransaction.ts b/packages/wallet/src/features/transactions/history/conversion/parseTradeTransaction.ts index f0ff2dd909a..9af9db0f6ee 100644 --- a/packages/wallet/src/features/transactions/history/conversion/parseTradeTransaction.ts +++ b/packages/wallet/src/features/transactions/history/conversion/parseTradeTransaction.ts @@ -41,7 +41,7 @@ export default function parseTradeTransaction( const chainId = fromGraphQLChain(transaction.chain) if (!chainId) { - return + return undefined } const txAssetChanges = @@ -83,14 +83,14 @@ export default function parseTradeTransaction( // Invalid input/output info if (!sent || !received) { - return + return undefined } const onlyERC20Tokens = sent.__typename === 'TokenTransfer' && received.__typename === 'TokenTransfer' const containsNFT = sent.__typename === 'NftTransfer' || received.__typename === 'NftTransfer' if (!(onlyERC20Tokens || containsNFT)) { - return + return undefined } // Token swap @@ -152,7 +152,7 @@ export default function parseTradeTransaction( } if (!inputCurrencyId || !outputCurrencyId) { - return + return undefined } return { @@ -222,4 +222,6 @@ export default function parseTradeTransaction( transactedUSDValue, } } + + return undefined } diff --git a/packages/wallet/src/features/transactions/history/utils.ts b/packages/wallet/src/features/transactions/history/utils.ts index d139ea5c033..155ac2966c6 100644 --- a/packages/wallet/src/features/transactions/history/utils.ts +++ b/packages/wallet/src/features/transactions/history/utils.ts @@ -282,8 +282,11 @@ function extractCurrencyIdFromTx(transaction: TransactionDetails | null): Curren // We only care about output currency because that's the net new asset return transaction.typeInfo.outputCurrencyId } + + return undefined } +// eslint-disable-next-line consistent-return export function remoteTxStatusToLocalTxStatus( type: RemoteTransactionType, status: RemoteTransactionStatus, diff --git a/packages/wallet/src/features/transactions/hooks.ts b/packages/wallet/src/features/transactions/hooks.ts index eaa4926bc91..e6ed9253a0a 100644 --- a/packages/wallet/src/features/transactions/hooks.ts +++ b/packages/wallet/src/features/transactions/hooks.ts @@ -34,7 +34,7 @@ export function usePendingTransactions( const transactions = useSelectAddressTransactions(address) return useMemo(() => { if (!transactions) { - return + return undefined } return transactions.filter( (tx: { status: TransactionStatus; typeInfo: { type: TransactionType } }) => @@ -69,7 +69,7 @@ export function useErroredQueuedOrders(address: Address | null): ErroredQueuedOr const transactions = useSelectAddressTransactions(address) return useMemo(() => { if (!transactions) { - return + return undefined } const erroredQueuedOrders: ErroredQueuedOrder[] = [] for (const tx of transactions) { @@ -86,7 +86,7 @@ export function useSortedPendingTransactions(address: Address | null): Transacti const transactions = usePendingTransactions(address) return useMemo(() => { if (!transactions) { - return + return undefined } return transactions.sort((a: TransactionDetails, b: TransactionDetails) => a.addedTime - b.addedTime) }, [transactions]) @@ -109,10 +109,14 @@ export function useCreateSwapFormState( const transaction = useSelectTransaction(address, chainId, txId) const inputCurrencyId = - transaction?.typeInfo.type === TransactionType.Swap ? transaction.typeInfo.inputCurrencyId : undefined + transaction?.typeInfo.type === TransactionType.Swap || transaction?.typeInfo.type === TransactionType.Bridge + ? transaction.typeInfo.inputCurrencyId + : undefined const outputCurrencyId = - transaction?.typeInfo.type === TransactionType.Swap ? transaction.typeInfo.outputCurrencyId : undefined + transaction?.typeInfo.type === TransactionType.Swap || transaction?.typeInfo.type === TransactionType.Bridge + ? transaction.typeInfo.outputCurrencyId + : undefined const inputCurrencyInfo = useCurrencyInfo(inputCurrencyId) const outputCurrencyInfo = useCurrencyInfo(outputCurrencyId) @@ -192,6 +196,7 @@ export function useMergeLocalAndRemoteTransactions( const orderHash = ensureLeading0x(tx.orderHash.toLowerCase()) return orderHashToTxHashMap.get(orderHash) ?? orderHash } + return undefined } const hashes = new Set() @@ -292,7 +297,7 @@ function useLowestPendingNonce(): BigNumberish | undefined { return useMemo(() => { let min: BigNumberish | undefined if (!pending) { - return + return undefined } pending.map((txn: TransactionDetails) => { if (isClassic(txn)) { diff --git a/packages/wallet/src/features/transactions/hooks/useAllTransactionsBetweenAddresses.ts b/packages/wallet/src/features/transactions/hooks/useAllTransactionsBetweenAddresses.ts index b8275a0c1f2..fa9dba5aab9 100644 --- a/packages/wallet/src/features/transactions/hooks/useAllTransactionsBetweenAddresses.ts +++ b/packages/wallet/src/features/transactions/hooks/useAllTransactionsBetweenAddresses.ts @@ -14,7 +14,7 @@ export function useAllTransactionsBetweenAddresses( const txnsToSearch = useSelectAddressTransactions(sender) return useMemo(() => { if (!sender || !recipient || !txnsToSearch) { - return + return undefined } return txnsToSearch.filter( (tx: TransactionDetails) => tx.typeInfo.type === TransactionType.Send && tx.typeInfo.recipient === recipient, diff --git a/packages/wallet/src/features/transactions/replaceTransactionSaga.ts b/packages/wallet/src/features/transactions/replaceTransactionSaga.ts index 1e99ec3e1b8..1ea7bada9c2 100644 --- a/packages/wallet/src/features/transactions/replaceTransactionSaga.ts +++ b/packages/wallet/src/features/transactions/replaceTransactionSaga.ts @@ -2,6 +2,7 @@ import { BigNumber, providers } from 'ethers' import { call, put, select } from 'typed-redux-saga' import { addTransaction, deleteTransaction } from 'uniswap/src/features/transactions/slice' import { + BridgeTransactionDetails, ClassicTransactionDetails, TransactionDetails, TransactionStatus, @@ -18,7 +19,7 @@ import { getProvider, getSignerManager } from 'wallet/src/features/wallet/contex import { selectAccounts } from 'wallet/src/features/wallet/selectors' export function* attemptReplaceTransaction( - transaction: ClassicTransactionDetails, + transaction: ClassicTransactionDetails | BridgeTransactionDetails, newTxRequest: providers.TransactionRequest, isCancellation = false, ) { diff --git a/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx b/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx index d61a12d80d6..cafda2c9dc5 100644 --- a/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx +++ b/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx @@ -23,6 +23,7 @@ import { useTransactionModalContext, } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +import { WalletChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { currencyAddress } from 'uniswap/src/utils/currencyId' import { NumberType } from 'utilities/src/format/types' @@ -90,7 +91,7 @@ export function SendReviewDetails({ const transferERC20Callback = useSendERC20Callback( txId, - chainId, + chainId as WalletChainId, recipient, currencyInInfo ? currencyAddress(currencyInInfo.currency) : undefined, currencyAmounts[CurrencyField.INPUT]?.quotient.toString(), @@ -102,7 +103,7 @@ export function SendReviewDetails({ const transferNFTCallback = useSendNFTCallback( txId, - chainId, + chainId as WalletChainId, recipient, nftIn?.nftContract?.address, nftIn?.tokenId, @@ -185,17 +186,17 @@ export function SendReviewDetails({ {transferWarning?.title && ( )} @@ -271,7 +272,7 @@ export function SendReviewDetails({ /> } - chainId={chainId} + chainId={chainId as WalletChainId} gasFee={gasFee} showWarning={Boolean(transferWarning)} warning={transferWarning} diff --git a/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts b/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts index 499dcb4c310..4bd0ff961a0 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts @@ -7,7 +7,7 @@ import { Erc1155, Erc20, Erc721 } from 'uniswap/src/abis/types' import { AssetType } from 'uniswap/src/entities/assets' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { DerivedSendInfo } from 'uniswap/src/features/transactions/send/types' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { currencyAddress, isNativeCurrencyAddress } from 'uniswap/src/utils/currencyId' import { useAsyncData } from 'utilities/src/react/hooks' @@ -25,7 +25,7 @@ export function useSendTransactionRequest(derivedSendInfo: DerivedSendInfo): pro const transactionFetcher = useCallback(() => { if (!provider) { - return + return undefined } return getSendTransaction(provider, contractManager, account, derivedSendInfo) @@ -34,6 +34,7 @@ export function useSendTransactionRequest(derivedSendInfo: DerivedSendInfo): pro return useAsyncData(transactionFetcher).data } +// eslint-disable-next-line consistent-return async function getSendTransaction( provider: providers.Provider, contractManager: ContractManager, @@ -42,7 +43,7 @@ async function getSendTransaction( ): Promise { const params = getSendParams(account, derivedSendInfo) if (!params) { - return + return undefined } const { type, tokenAddress, chainId } = params @@ -58,6 +59,7 @@ async function getSendTransaction( } } +// eslint-disable-next-line consistent-return function getSendParams(account: Account, derivedSendInfo: DerivedSendInfo): SendTokenParams | undefined { const { currencyAmounts, currencyTypes, chainId, recipient, currencyInInfo, nftIn } = derivedSendInfo const tokenAddress = currencyInInfo ? currencyAddress(currencyInInfo.currency) : nftIn?.nftContract?.address @@ -65,19 +67,19 @@ function getSendParams(account: Account, derivedSendInfo: DerivedSendInfo): Send const assetType = currencyTypes[CurrencyField.INPUT] if (!chainId || !tokenAddress || !recipient || !assetType) { - return + return undefined } switch (assetType) { case AssetType.ERC1155: case AssetType.ERC721: { if (!nftIn) { - return + return undefined } return { account, - chainId, + chainId: chainId as WalletChainId, toAddress: recipient, tokenAddress, type: assetType, @@ -87,12 +89,12 @@ function getSendParams(account: Account, derivedSendInfo: DerivedSendInfo): Send case AssetType.Currency: { if (!currencyInInfo || amount === undefined) { - return + return undefined } return { account, - chainId, + chainId: chainId as WalletChainId, toAddress: recipient, tokenAddress, type: AssetType.Currency, diff --git a/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts b/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts index e1131041423..6b0ceb9860d 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts @@ -25,7 +25,7 @@ export function getSendWarnings(t: TFunction, derivedSendInfo: DerivedSendInfo, const isMissingRequiredParams = checkIsMissingRequiredParams( currencyInInfo, nftIn, - chainId, + chainId as WalletChainId, recipient, !!currencyAmountIn, !!currencyBalanceIn, diff --git a/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts b/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts index 0bca10f78f4..effffeeb073 100644 --- a/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts +++ b/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts @@ -106,6 +106,7 @@ describe(sendTransaction, () => { maxPriorityFeePerGas: undefined, maxFeePerGas: undefined, }, + timeoutTimestampMs: undefined, }, }), ) diff --git a/packages/wallet/src/features/transactions/sendTransactionSaga.ts b/packages/wallet/src/features/transactions/sendTransactionSaga.ts index 8a7254d4664..ea082a206c6 100644 --- a/packages/wallet/src/features/transactions/sendTransactionSaga.ts +++ b/packages/wallet/src/features/transactions/sendTransactionSaga.ts @@ -7,6 +7,7 @@ import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/gating/fl import { Statsig } from 'uniswap/src/features/gating/sdk/statsig' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { UniverseEventProperties } from 'uniswap/src/features/telemetry/types' import { makeSelectAddressTransactions } from 'uniswap/src/features/transactions/selectors' import { transactionActions } from 'uniswap/src/features/transactions/slice' import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' @@ -18,16 +19,26 @@ import { TransactionStatus, TransactionType, TransactionTypeInfo, + isBridgeTypeInfo, } from 'uniswap/src/features/transactions/types/transactionDetails' import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { logger } from 'utilities/src/logger/logger' +import { ONE_MINUTE_MS } from 'utilities/src/time/time' import { isPrivateRpcSupportedOnChain } from 'wallet/src/features/providers/utils' import { getSerializableTransactionRequest } from 'wallet/src/features/transactions/utils' import { getPrivateProvider, getProvider, getSignerManager } from 'wallet/src/features/wallet/context' import { SignerManager } from 'wallet/src/features/wallet/signing/SignerManager' import { hexlifyTransaction } from 'wallet/src/utils/transaction' +// This timeout is used to trigger a log event if the transaction is pending for too long +const getTransactionTimeoutMs = (chainId: WalletChainId) => { + if (chainId === UniverseChainId.Mainnet) { + return 10 * ONE_MINUTE_MS + } + return ONE_MINUTE_MS +} + export interface SendTransactionParams { // internal id used for tracking transactions before they're submitted // this is optional as an override in txDetail.id calculation @@ -112,9 +123,10 @@ function* addTransaction( ) { const id = txId ?? createTransactionId() const request = getSerializableTransactionRequest(populatedRequest, chainId) + const timeoutTimestampMs = typeInfo.gasEstimates ? Date.now() + getTransactionTimeoutMs(chainId) : undefined const transaction: TransactionDetails = { - routing: Routing.CLASSIC, + routing: isBridgeTypeInfo(typeInfo) ? Routing.BRIDGE : Routing.CLASSIC, id, chainId, hash, @@ -125,11 +137,12 @@ function* addTransaction( options: { ...options, request, + timeoutTimestampMs, }, transactionOriginType, } - if (transaction.typeInfo.type === TransactionType.Swap) { + if (transaction.typeInfo.type === TransactionType.Swap || transaction.typeInfo.type === TransactionType.Bridge) { if (!analytics) { // Don't expect swaps from WC or Dapps to always provide analytics object if (transactionOriginType === TransactionOriginType.Internal) { @@ -139,11 +152,12 @@ function* addTransaction( }) } } else { - yield* call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, { - routing: transaction.routing, + const event: UniverseEventProperties[WalletEventName.SwapSubmitted] = { + routing: transaction.routing === Routing.BRIDGE ? 'BRIDGE' : 'CLASSIC', transaction_hash: hash, ...analytics, - }) + } + yield* call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, event) } } yield* put(transactionActions.addTransaction(transaction)) diff --git a/packages/wallet/src/features/transactions/swap/BridgingModal.tsx b/packages/wallet/src/features/transactions/swap/BridgingModal.tsx new file mode 100644 index 00000000000..dbbd66d5ad1 --- /dev/null +++ b/packages/wallet/src/features/transactions/swap/BridgingModal.tsx @@ -0,0 +1,74 @@ +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch } from 'react-redux' +import { Checkbox, Flex, Text, TouchableArea } from 'ui/src' +import { Shuffle } from 'ui/src/components/icons' +import { iconSizes } from 'ui/src/theme' +import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' +import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' +import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' +import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { UniverseChainId } from 'uniswap/src/types/chains' +import { setHasDismissedBridgingWarning } from 'wallet/src/features/behaviorHistory/slice' + +export function BridgingModal({ + isOpen, + onContinue, + onClose, + fromNetwork, + toNetwork, +}: { + isOpen: boolean + onClose: () => void + onContinue: () => void + fromNetwork: UniverseChainId + toNetwork: UniverseChainId +}): JSX.Element { + const { t } = useTranslation() + const dispatch = useDispatch() + + const [doNotShowAgainPressed, setDoNotShowAgainPressed] = useState(false) + const onPressDoNotShowAgain = useCallback(() => { + const toggled = !doNotShowAgainPressed + dispatch(setHasDismissedBridgingWarning(toggled)) + setDoNotShowAgainPressed(toggled) + }, [doNotShowAgainPressed, dispatch]) + + const icon = ( + + + + + + ) + + return ( + + + + + + {t('common.dontShowAgain')} + + + + + ) +} diff --git a/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts b/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts index 811d2c1bc28..ca2d46cc604 100644 --- a/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts +++ b/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts @@ -1,7 +1,11 @@ import { Currency, TradeType } from '@uniswap/sdk-core' import { AssetType, CurrencyAsset } from 'uniswap/src/entities/assets' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' -import { TransactionDetails, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' +import { + TransactionDetails, + TransactionType, + isBridgeTypeInfo, +} from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' import { CurrencyField } from 'uniswap/src/types/currency' import { currencyAddress, currencyIdToAddress } from 'uniswap/src/utils/currencyId' @@ -31,8 +35,9 @@ export function createSwapFormFromTxDetails({ try { const { typeInfo } = transactionDetails + const isBridging = isBridgeTypeInfo(typeInfo) - if (typeInfo.type !== TransactionType.Swap) { + if (typeInfo.type !== TransactionType.Swap && !isBridging) { throw new Error(`Tx hash ${txHash} does not correspond to a swap tx. It is of type ${typeInfo.type}`) } @@ -52,8 +57,11 @@ export function createSwapFormFromTxDetails({ type: AssetType.Currency, } - const exactCurrencyField = - typeInfo.tradeType === TradeType.EXACT_OUTPUT ? CurrencyField.OUTPUT : CurrencyField.INPUT + const exactCurrencyField = isBridging + ? CurrencyField.INPUT + : typeInfo.tradeType === TradeType.EXACT_OUTPUT + ? CurrencyField.OUTPUT + : CurrencyField.INPUT const { value, currency } = exactCurrencyField === CurrencyField.INPUT @@ -74,6 +82,7 @@ export function createSwapFormFromTxDetails({ logger.error(error, { tags: { file: 'createSwapFormFromTxDetails', function: 'createSwapFormFromTxDetails' }, }) + return undefined } } @@ -134,5 +143,6 @@ export function createWrapFormFromTxDetails({ logger.error(error, { tags: { file: 'createSwapFormFromTxDetails', function: 'createWrapFormFromTxDetails' }, }) + return undefined } } diff --git a/packages/wallet/src/features/transactions/swap/hooks/useMostRecentSwapTx.ts b/packages/wallet/src/features/transactions/swap/hooks/useMostRecentSwapTx.ts index 94e7f156993..413db93a332 100644 --- a/packages/wallet/src/features/transactions/swap/hooks/useMostRecentSwapTx.ts +++ b/packages/wallet/src/features/transactions/swap/hooks/useMostRecentSwapTx.ts @@ -6,9 +6,10 @@ import { flattenObjectOfObjects } from 'utilities/src/primitives/objects' export function useMostRecentSwapTx(address: Address): TransactionDetails | undefined { const transactions = useSelector(selectTransactions) const addressTransactions = transactions[address] - if (addressTransactions) { - return flattenObjectOfObjects(addressTransactions) - .filter((tx) => tx.typeInfo.type === TransactionType.Swap) - .sort((a, b) => b.addedTime - a.addedTime)[0] + if (!addressTransactions) { + return undefined } + return flattenObjectOfObjects(addressTransactions) + .filter((tx) => tx.typeInfo.type === TransactionType.Swap) + .sort((a, b) => b.addedTime - a.addedTime)[0] } diff --git a/packages/wallet/src/features/transactions/swap/hooks/useSwapCallback.ts b/packages/wallet/src/features/transactions/swap/hooks/useSwapCallback.ts index 9a12242a47c..64cfbed7953 100644 --- a/packages/wallet/src/features/transactions/swap/hooks/useSwapCallback.ts +++ b/packages/wallet/src/features/transactions/swap/hooks/useSwapCallback.ts @@ -1,6 +1,7 @@ import { SwapEventName } from '@uniswap/analytics-events' import { useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { selectSwapStartTimestamp } from 'uniswap/src/features/timing/selectors' @@ -24,9 +25,8 @@ export function useSwapCallback(): SwapCallback { account, swapTxContext, txId, - onSubmit, + onSuccess, onFailure, - steps, currencyInAmountUSD, currencyOutAmountUSD, isAutoSlippage, @@ -34,8 +34,13 @@ export function useSwapCallback(): SwapCallback { } = args const { trade, gasFee } = swapTxContext + // unsigned (missing permit signature) swaps are only supported on interface; this is an unreachable state and the following check is included for type safety. + if (swapTxContext.routing === Routing.CLASSIC && swapTxContext.unsigned) { + throw new Error('Swaps with async signatures are not implemented for wallet') + } + const analytics = getBaseTradeAnalyticsProperties({ formatter, trade, currencyInAmountUSD, currencyOutAmountUSD }) - appDispatch(swapActions.trigger({ swapTxContext, txId, account, analytics, steps, onSubmit, onFailure })) + appDispatch(swapActions.trigger({ swapTxContext, txId, account, analytics, onSuccess, onFailure })) const blockNumber = getClassicQuoteFromResponse(trade?.quote)?.blockNumber?.toString() diff --git a/packages/wallet/src/features/transactions/swap/modals/QueuedOrderModal.tsx b/packages/wallet/src/features/transactions/swap/modals/QueuedOrderModal.tsx index 660a92ceb51..2d95247e1d4 100644 --- a/packages/wallet/src/features/transactions/swap/modals/QueuedOrderModal.tsx +++ b/packages/wallet/src/features/transactions/swap/modals/QueuedOrderModal.tsx @@ -142,7 +142,7 @@ function useTransactionState(transaction: TransactionDetails | undefined): Trans return useMemo(() => { if (!isSwap || !inputCurrency || !outputCurrency) { - return + return undefined } const input: TradeableAsset = { diff --git a/packages/wallet/src/features/transactions/swap/modals/SwapProtectionModal.tsx b/packages/wallet/src/features/transactions/swap/modals/SwapProtectionModal.tsx index a7db1c078bb..0d5775a9107 100644 --- a/packages/wallet/src/features/transactions/swap/modals/SwapProtectionModal.tsx +++ b/packages/wallet/src/features/transactions/swap/modals/SwapProtectionModal.tsx @@ -14,7 +14,7 @@ export function SwapProtectionInfoModal({ isOpen, onClose }: { isOpen: boolean; } isOpen={isOpen} modalName={ModalName.SwapProtection} diff --git a/packages/wallet/src/features/transactions/swap/submitOrderSaga.test.ts b/packages/wallet/src/features/transactions/swap/submitOrderSaga.test.ts index 64ff4d20785..b685bb51c9a 100644 --- a/packages/wallet/src/features/transactions/swap/submitOrderSaga.test.ts +++ b/packages/wallet/src/features/transactions/swap/submitOrderSaga.test.ts @@ -2,10 +2,11 @@ import { Protocol } from '@uniswap/router-sdk' import { TradeType } from '@uniswap/sdk-core' import { testSaga } from 'redux-saga-test-plan' import { submitOrder } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' -import { OrderRequest, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { DutchOrderInfo, DutchQuoteV2, OrderRequest, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' +import { signTypedData } from 'uniswap/src/features/transactions/signing' import { addTransaction, finalizeTransaction, updateTransaction } from 'uniswap/src/features/transactions/slice' import { QueuedOrderStatus, @@ -15,6 +16,7 @@ import { UniswapXOrderDetails, } from 'uniswap/src/features/transactions/types/transactionDetails' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' +import { mockPermit } from 'uniswap/src/test/fixtures/permit' import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyId } from 'uniswap/src/utils/currencyId' import { pushNotification } from 'wallet/src/features/notifications/slice' @@ -24,8 +26,15 @@ import { SubmitUniswapXOrderParams, submitUniswapXOrder, } from 'wallet/src/features/transactions/swap/submitOrderSaga' +import { getSignerManager } from 'wallet/src/features/wallet/context' import { signerMnemonicAccount } from 'wallet/src/test/fixtures' +const mockSignature = '0xMockSignature' +const mockSigner = {} +const mockSignerManager = { + getSignerForAccount: jest.fn(), +} + const baseSubmitOrderParams = { chainId: UniverseChainId.Mainnet, account: signerMnemonicAccount(), @@ -43,14 +52,19 @@ const baseSubmitOrderParams = { transactionOriginType: TransactionOriginType.Internal, }, txId: '1', - orderParams: { quote: { orderId: '0xMockOrderHash' } } as unknown as OrderRequest, - onSubmit: jest.fn(), + onSuccess: jest.fn(), onFailure: jest.fn(), + quote: { + orderId: '0xMockOrderHash', + encodedOrder: '0xMockEncodedOrder', + orderInfo: {} as DutchOrderInfo, + } as unknown as DutchQuoteV2, + permit: mockPermit, } satisfies SubmitUniswapXOrderParams const baseExpectedInitialOrderDetails: UniswapXOrderDetails = { routing: Routing.DUTCH_V2, - orderHash: baseSubmitOrderParams.orderParams.quote.orderId, + orderHash: '0xMockOrderHash', id: baseSubmitOrderParams.txId, chainId: baseSubmitOrderParams.chainId, typeInfo: baseSubmitOrderParams.typeInfo, @@ -61,6 +75,12 @@ const baseExpectedInitialOrderDetails: UniswapXOrderDetails = { transactionOriginType: TransactionOriginType.Internal, } +const expectedOrderRequest: OrderRequest = { + signature: mockSignature, + quote: baseSubmitOrderParams.quote, + routing: Routing.DUTCH_V2, +} + describe(submitUniswapXOrder, () => { beforeEach(() => { let mockTimestamp = 1 @@ -80,7 +100,13 @@ describe(submitUniswapXOrder, () => { .next() .put({ type: updateTransaction.type, payload: expectedSubmittedOrderDetails }) .next() - .call(submitOrder, baseSubmitOrderParams.orderParams) + .call(getSignerManager) + .next(mockSignerManager) + .call([mockSignerManager, 'getSignerForAccount'], baseSubmitOrderParams.account) + .next(mockSigner) + .call(signTypedData, mockPermit.domain, mockPermit.types, mockPermit.values, mockSigner) + .next(mockSignature) + .call(submitOrder, expectedOrderRequest) .next() .call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, { routing: Routing.DUTCH_V2, @@ -90,7 +116,7 @@ describe(submitUniswapXOrder, () => { .next() .put(pushNotification({ type: AppNotificationType.SwapPending, wrapType: WrapType.NotApplicable })) .next() - .call(baseSubmitOrderParams.onSubmit) + .call(baseSubmitOrderParams.onSuccess) .next() .isDone() }) @@ -108,7 +134,13 @@ describe(submitUniswapXOrder, () => { .next() .put({ type: updateTransaction.type, payload: expectedSubmittedOrderDetails }) .next() - .call(submitOrder, baseSubmitOrderParams.orderParams) + .call(getSignerManager) + .next(mockSignerManager) + .call([mockSignerManager, 'getSignerForAccount'], baseSubmitOrderParams.account) + .next(mockSigner) + .call(signTypedData, mockPermit.domain, mockPermit.types, mockPermit.values, mockSigner) + .next(mockSignature) + .call(submitOrder, expectedOrderRequest) .throw(new Error('pretend the order endpoint failed')) .put({ type: updateTransaction.type, @@ -144,7 +176,13 @@ describe(submitUniswapXOrder, () => { .next({ payload: { hash: approveTxHash, status: TransactionStatus.Success } }) .put({ type: updateTransaction.type, payload: expectedSubmittedOrderDetails }) .next() - .call(submitOrder, baseSubmitOrderParams.orderParams) + .call(getSignerManager) + .next(mockSignerManager) + .call([mockSignerManager, 'getSignerForAccount'], baseSubmitOrderParams.account) + .next(mockSigner) + .call(signTypedData, mockPermit.domain, mockPermit.types, mockPermit.values, mockSigner) + .next(mockSignature) + .call(submitOrder, expectedOrderRequest) .next() .call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, { routing: Routing.DUTCH_V2, @@ -154,7 +192,7 @@ describe(submitUniswapXOrder, () => { .next() .put(pushNotification({ type: AppNotificationType.SwapPending, wrapType: WrapType.NotApplicable })) .next() - .call(baseSubmitOrderParams.onSubmit) + .call(baseSubmitOrderParams.onSuccess) .next() .isDone() }) @@ -176,7 +214,13 @@ describe(submitUniswapXOrder, () => { .next({ payload: { hash: wrapTxHash, status: TransactionStatus.Success } }) .put({ type: updateTransaction.type, payload: expectedSubmittedOrderDetails }) .next() - .call(submitOrder, baseSubmitOrderParams.orderParams) + .call(getSignerManager) + .next(mockSignerManager) + .call([mockSignerManager, 'getSignerForAccount'], baseSubmitOrderParams.account) + .next(mockSigner) + .call(signTypedData, mockPermit.domain, mockPermit.types, mockPermit.values, mockSigner) + .next(mockSignature) + .call(submitOrder, expectedOrderRequest) .next() .call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, { routing: Routing.DUTCH_V2, @@ -186,7 +230,7 @@ describe(submitUniswapXOrder, () => { .next() .put(pushNotification({ type: AppNotificationType.SwapPending, wrapType: WrapType.NotApplicable })) .next() - .call(baseSubmitOrderParams.onSubmit) + .call(baseSubmitOrderParams.onSuccess) .next() .isDone() }) @@ -208,7 +252,13 @@ describe(submitUniswapXOrder, () => { .next({ payload: { hash: approveTxHash, status: TransactionStatus.Success } }) .put({ type: updateTransaction.type, payload: expectedSubmittedOrderDetails }) .next() - .call(submitOrder, baseSubmitOrderParams.orderParams) + .call(getSignerManager) + .next(mockSignerManager) + .call([mockSignerManager, 'getSignerForAccount'], baseSubmitOrderParams.account) + .next(mockSigner) + .call(signTypedData, mockPermit.domain, mockPermit.types, mockPermit.values, mockSigner) + .next(mockSignature) + .call(submitOrder, expectedOrderRequest) .next() .call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, { routing: Routing.DUTCH_V2, @@ -218,7 +268,7 @@ describe(submitUniswapXOrder, () => { .next() .put(pushNotification({ type: AppNotificationType.SwapPending, wrapType: WrapType.NotApplicable })) .next() - .call(baseSubmitOrderParams.onSubmit) + .call(baseSubmitOrderParams.onSuccess) .next() .isDone() }) diff --git a/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts b/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts index bac57ab5bcd..6354f0d4cba 100644 --- a/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts +++ b/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts @@ -1,11 +1,13 @@ import { call, put, take } from 'typed-redux-saga' import { submitOrder } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' -import { OrderRequest, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { DutchQuoteV2, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' import { AccountMeta } from 'uniswap/src/features/accounts/types' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { signTypedData } from 'uniswap/src/features/transactions/signing' import { finalizeTransaction, transactionActions } from 'uniswap/src/features/transactions/slice' import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' +import { ValidatedPermit } from 'uniswap/src/features/transactions/swap/utils/trade' import { QueuedOrderStatus, TransactionOriginType, @@ -20,6 +22,7 @@ import { logger } from 'utilities/src/logger/logger' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { pushNotification } from 'wallet/src/features/notifications/slice' import { AppNotificationType } from 'wallet/src/features/notifications/types' +import { getSignerManager } from 'wallet/src/features/wallet/context' // If the app is closed during the waiting period and then reopened, the saga will resume; // the order should not be submitted if too much time has passed as it may be stale. @@ -28,26 +31,39 @@ export const ORDER_STALENESS_THRESHOLD = 45 * ONE_SECOND_MS export interface SubmitUniswapXOrderParams { // internal id used for tracking transactions before they're submitted txId?: string + quote: DutchQuoteV2 + permit: ValidatedPermit chainId: WalletChainId - orderParams: OrderRequest account: AccountMeta typeInfo: TransactionTypeInfo analytics: ReturnType approveTxHash?: string wrapTxHash?: string - onSubmit: () => void + onSuccess: () => void onFailure: () => void } export function* submitUniswapXOrder(params: SubmitUniswapXOrderParams) { - const { orderParams, approveTxHash, wrapTxHash, txId, chainId, typeInfo, account, analytics, onSubmit, onFailure } = - params + const { + quote, + permit, + approveTxHash, + wrapTxHash, + txId, + chainId, + typeInfo, + account, + analytics, + onSuccess, + onFailure, + } = params + + const orderHash = quote.orderId // Wait for approval and/or wrap transactions to confirm, otherwise order submission will fail. let waitingForApproval = Boolean(approveTxHash) let waitingForWrap = Boolean(wrapTxHash) - const orderHash = orderParams.quote.orderId const order = { routing: Routing.DUTCH_V2, orderHash, @@ -97,7 +113,13 @@ export function* submitUniswapXOrder(params: SubmitUniswapXOrderParams) { try { const addedTime = Date.now() // refresh the addedTime to match the actual submission time yield* put(transactionActions.updateTransaction({ ...order, queueStatus: QueuedOrderStatus.Submitted, addedTime })) - yield* call(submitOrder, orderParams) + + const signerManager = yield* call(getSignerManager) + const signer = yield* call([signerManager, 'getSignerForAccount'], account) + + const signature = yield* call(signTypedData, permit.domain, permit.types, permit.values, signer) + + yield* call(submitOrder, { signature, quote, routing: Routing.DUTCH_V2 }) } catch { // In the rare event that submission fails, we update the order status to prompt the user. // If the app is closed before this catch block is reached, orderWatcherSaga will handle the failure upon reopening. @@ -110,6 +132,6 @@ export function* submitUniswapXOrder(params: SubmitUniswapXOrderParams) { yield* call(sendAnalyticsEvent, WalletEventName.SwapSubmitted, properties) yield* put(pushNotification({ type: AppNotificationType.SwapPending, wrapType: WrapType.NotApplicable })) - // onSubmit does not need to be wrapped in yield* call() here, but doing so makes it easier to test call ordering in submitOrder.test.ts - yield* call(onSubmit) + // onSuccess does not need to be wrapped in yield* call() here, but doing so makes it easier to test call ordering in submitOrder.test.ts + yield* call(onSuccess) } diff --git a/packages/wallet/src/features/transactions/swap/swapSaga.test.ts b/packages/wallet/src/features/transactions/swap/swapSaga.test.ts index 665df77afb7..ef26255ea1d 100644 --- a/packages/wallet/src/features/transactions/swap/swapSaga.test.ts +++ b/packages/wallet/src/features/transactions/swap/swapSaga.test.ts @@ -8,7 +8,7 @@ import JSBI from 'jsbi' import { expectSaga, testSaga } from 'redux-saga-test-plan' import { EffectProviders, StaticProvider } from 'redux-saga-test-plan/providers' import { DAI, USDC } from 'uniswap/src/constants/tokens' -import { OrderRequest, Routing } from 'uniswap/src/data/tradingApi/__generated__/index' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' import { ClassicTrade, UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade' @@ -20,6 +20,7 @@ import { } from 'uniswap/src/features/transactions/types/transactionDetails' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' import { WETH } from 'uniswap/src/test/fixtures' +import { mockPermit } from 'uniswap/src/test/fixtures/permit' import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyId } from 'uniswap/src/utils/currencyId' import { pushNotification } from 'wallet/src/features/notifications/slice' @@ -125,13 +126,11 @@ const classicSwapParams = { gasFee: { value: '5', isLoading: false, error: null }, gasFeeEstimation: {}, approvalError: false, - permitData: undefined, - permitDataLoading: false, - permitSignature: undefined, + permit: undefined, swapRequestArgs: undefined, + unsigned: false, }, - steps: [], - onSubmit: jest.fn(), + onSuccess: jest.fn(), onFailure: jest.fn(), } satisfies SwapParams @@ -147,16 +146,14 @@ const uniswapXSwapParams = { revocationTxRequest: mockRevocationTxRequest, trade: mockUniswapXTrade, indicativeTrade: undefined, - orderParams: { quote: { orderId: '0xMockOrderHash' } } as unknown as OrderRequest, + permit: mockPermit, wrapTxRequest: undefined, gasFee: { value: '5', isLoading: false, error: null }, + gasFeeEstimation: {}, gasFeeBreakdown: { classicGasUseEstimateUSD: '5', approvalCost: '5', wrapCost: '0' }, approvalError: false, - permitData: undefined, - permitDataLoading: false, }, - steps: [], - onSubmit: jest.fn(), + onSuccess: jest.fn(), onFailure: jest.fn(), } satisfies SwapParams @@ -223,7 +220,7 @@ describe(approveAndSwap, () => { // Requires manually providing return values for each effect in `.next()`. testSaga(approveAndSwap, classicSwapParamsWithoutApprove) .next() - .call(classicSwapParams.onSubmit) + .call(classicSwapParams.onSuccess) .next() .call(shouldSubmitViaPrivateRpc, classicSwapParams.swapTxContext.txRequest.chainId) .next(false) @@ -262,7 +259,7 @@ describe(approveAndSwap, () => { .silentRun() testSaga(approveAndSwap, classicSwapParams) .next() - .call(classicSwapParams.onSubmit) + .call(classicSwapParams.onSuccess) .next() .call(shouldSubmitViaPrivateRpc, classicSwapParams.swapTxContext.txRequest.chainId) .next(false) @@ -286,8 +283,9 @@ describe(approveAndSwap, () => { approveTxHash: '0xMockApprovalTxHash', wrapTxHash: undefined, txId: uniswapXSwapParams.txId, - orderParams: uniswapXSwapParams.swapTxContext.orderParams, - onSubmit: uniswapXSwapParams.onSubmit, + permit: mockPermit, + quote: uniswapXSwapParams.swapTxContext.trade.quote.quote, + onSuccess: uniswapXSwapParams.onSuccess, onFailure: uniswapXSwapParams.onFailure, } @@ -320,7 +318,8 @@ describe(approveAndSwap, () => { swapTxContext: { ...uniswapXSwapParams.swapTxContext, wrapTxRequest: mockWrapTxRequest, - permitDataLoading: false, + permit: mockPermit, + gasFeeEstimation: {}, }, } satisfies SwapParams @@ -333,6 +332,7 @@ describe(approveAndSwap, () => { unwrapped: false, currencyAmountRaw: '1000', swapTxId: '1', + gasEstimates: undefined, }, txId: undefined, transactionOriginType: TransactionOriginType.Internal, @@ -346,9 +346,10 @@ describe(approveAndSwap, () => { approveTxHash: '0xMockApprovalTxHash', wrapTxHash: '0xMockWrapTxHash', txId: uniswapXSwapParams.txId, - orderParams: uniswapXSwapParams.swapTxContext.orderParams, - onSubmit: uniswapXSwapParams.onSubmit, + permit: mockPermit, + onSuccess: uniswapXSwapParams.onSuccess, onFailure: uniswapXSwapParams.onFailure, + quote: uniswapXSwapParams.swapTxContext.trade.quote.quote, } await expectSaga(approveAndSwap, uniswapXSwapEthInputParams) diff --git a/packages/wallet/src/features/transactions/swap/swapSaga.ts b/packages/wallet/src/features/transactions/swap/swapSaga.ts index 4f35de5452e..15538f19348 100644 --- a/packages/wallet/src/features/transactions/swap/swapSaga.ts +++ b/packages/wallet/src/features/transactions/swap/swapSaga.ts @@ -6,7 +6,6 @@ import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/gating/fl import { Statsig } from 'uniswap/src/features/gating/sdk/statsig' import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' import { ValidatedSwapTxContext } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' -import { TransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateSwapSteps' import { tradeToTransactionInfo } from 'uniswap/src/features/transactions/swap/utils/trade' import { ApproveTransactionInfo, @@ -19,7 +18,7 @@ import { pushNotification } from 'wallet/src/features/notifications/slice' import { AppNotificationType } from 'wallet/src/features/notifications/types' import { isPrivateRpcSupportedOnChain } from 'wallet/src/features/providers/utils' import { sendTransaction, tryGetNonce } from 'wallet/src/features/transactions/sendTransactionSaga' -import { submitUniswapXOrder } from 'wallet/src/features/transactions/swap/submitOrderSaga' +import { SubmitUniswapXOrderParams, submitUniswapXOrder } from 'wallet/src/features/transactions/swap/submitOrderSaga' import { wrap } from 'wallet/src/features/transactions/swap/wrapSaga' import { selectWalletSwapProtectionSetting } from 'wallet/src/features/wallet/selectors' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' @@ -30,23 +29,23 @@ export type SwapParams = { account: SignerMnemonicAccountMeta analytics: ReturnType swapTxContext: ValidatedSwapTxContext - steps: TransactionStep[] - onSubmit: () => void + onSuccess: () => void onFailure: () => void } export function* approveAndSwap(params: SwapParams) { try { - const { swapTxContext, account, txId, analytics, onSubmit, onFailure } = params - const { trade, routing, approveTxRequest } = swapTxContext + const { swapTxContext, account, txId, analytics, onSuccess, onFailure } = params + const { routing, approveTxRequest } = swapTxContext const isUniswapX = routing === Routing.DUTCH_V2 + const isBridge = routing === Routing.BRIDGE const chainId = swapTxContext.trade.inputAmount.currency.chainId // For classic swaps, trigger UI changes immediately after click if (!isUniswapX) { - // onSubmit does not need to be wrapped in yield* call() here, but doing so makes it easier to test call ordering in swapSaga.test.ts - yield* call(onSubmit) + // onSuccess does not need to be wrapped in yield* call() here, but doing so makes it easier to test call ordering in swapSaga.test.ts + yield* call(onSuccess) } // MEV protection is not needed for UniswapX approval and/or wrap transactions. @@ -55,7 +54,7 @@ export function* approveAndSwap(params: SwapParams) { // otherwise for some L2s the Provider might fetch the same nonce for both transactions. let nonce = yield* call(tryGetNonce, account, chainId) - const gasFeeEstimation = swapTxContext.routing === Routing.CLASSIC ? swapTxContext.gasFeeEstimation : undefined + const gasFeeEstimation = swapTxContext.gasFeeEstimation let approveTxHash: string | undefined // Approval Logic @@ -90,7 +89,8 @@ export function* approveAndSwap(params: SwapParams) { const typeInfo = tradeToTransactionInfo(swapTxContext.trade, transactedUSDValue, gasFeeEstimation?.swapEstimates) // Swap Logic - UniswapX if (isUniswapX) { - const { orderParams, wrapTxRequest } = swapTxContext + const { trade, wrapTxRequest, permit } = swapTxContext + const quote = trade.quote.quote let wrapTxHash: string | undefined // Wrap Logic - UniswapX Eth-input @@ -102,26 +102,39 @@ export function* approveAndSwap(params: SwapParams) { inputCurrencyAmount, swapTxId: txId, skipPushNotification: true, // wrap is abstracted away in UX; we avoid showing a wrap notification + gasEstimates: gasFeeEstimation?.wrapEstimates, }) wrapTxHash = wrapResponse?.transactionResponse.hash } - const submitOrderParams = { - txId, - chainId, - orderParams, + const submitOrderParams: SubmitUniswapXOrderParams = { + account, + analytics, approveTxHash, wrapTxHash, - account, + permit, + quote, typeInfo, - analytics, - onSubmit, + chainId, + txId, + onSuccess, onFailure, } yield* call(submitUniswapXOrder, submitOrderParams) - } - // Swap Logic - Classic - else { + } else if (isBridge) { + const options = { request: { ...swapTxContext.txRequest, nonce }, submitViaPrivateRpc } + const sendTransactionParams = { + txId, + chainId, + account, + options, + typeInfo, + analytics, + transactionOriginType: TransactionOriginType.Internal, + } + yield* call(sendTransaction, sendTransactionParams) + yield* put(pushNotification({ type: AppNotificationType.SwapPending, wrapType: WrapType.NotApplicable })) + } else { const options = { request: { ...swapTxContext.txRequest, nonce }, submitViaPrivateRpc } const sendTransactionParams = { txId, diff --git a/packages/wallet/src/features/transactions/swap/wrapSaga.test.ts b/packages/wallet/src/features/transactions/swap/wrapSaga.test.ts index ba181365fd6..b777bf4b671 100644 --- a/packages/wallet/src/features/transactions/swap/wrapSaga.test.ts +++ b/packages/wallet/src/features/transactions/swap/wrapSaga.test.ts @@ -22,6 +22,7 @@ const wrapTxInfo: WrapTransactionInfo = { unwrapped: false, currencyAmountRaw: '200000', swapTxId: undefined, + gasEstimates: undefined, } const unwrapTxInfo: WrapTransactionInfo = { diff --git a/packages/wallet/src/features/transactions/swap/wrapSaga.ts b/packages/wallet/src/features/transactions/swap/wrapSaga.ts index d821102e605..e41bb8a8b1b 100644 --- a/packages/wallet/src/features/transactions/swap/wrapSaga.ts +++ b/packages/wallet/src/features/transactions/swap/wrapSaga.ts @@ -3,6 +3,7 @@ import { providers } from 'ethers' import { call, put } from 'typed-redux-saga' import { AccountMeta } from 'uniswap/src/features/accounts/types' import { + GasFeeEstimates, TransactionOptions, TransactionOriginType, TransactionType, @@ -23,11 +24,12 @@ export type WrapParams = { account: AccountMeta inputCurrencyAmount: CurrencyAmount skipPushNotification?: boolean + gasEstimates?: GasFeeEstimates } export function* wrap(params: WrapParams) { try { - const { account, inputCurrencyAmount, txRequest, txId, skipPushNotification, swapTxId } = params + const { account, inputCurrencyAmount, txRequest, txId, skipPushNotification, swapTxId, gasEstimates } = params let typeInfo: TransactionTypeInfo if (inputCurrencyAmount.currency.isNative) { @@ -36,6 +38,7 @@ export function* wrap(params: WrapParams) { unwrapped: false, currencyAmountRaw: inputCurrencyAmount.quotient.toString(), swapTxId, + gasEstimates, } } else { typeInfo = { @@ -43,6 +46,7 @@ export function* wrap(params: WrapParams) { unwrapped: true, currencyAmountRaw: inputCurrencyAmount.quotient.toString(), swapTxId, + gasEstimates, } } @@ -67,6 +71,7 @@ export function* wrap(params: WrapParams) { return result } catch (error) { logger.error(error, { tags: { file: 'wrapSaga', function: 'wrap' } }) + return undefined } } diff --git a/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts b/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts index de7aaac81da..ea3e9cdda56 100644 --- a/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts +++ b/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts @@ -206,6 +206,7 @@ describe(watchFiatOnRampTransaction, () => { return confirmedTx } } + return undefined }, }, [delay(PollingInterval.Fast), Promise.resolve(() => undefined)], @@ -238,6 +239,7 @@ describe(watchFiatOnRampTransaction, () => { return confirmedTx } } + return undefined }, }, ]) diff --git a/packages/wallet/src/features/transactions/transactionWatcherSaga.ts b/packages/wallet/src/features/transactions/transactionWatcherSaga.ts index ddeef0bffe4..7bd1e1d174e 100644 --- a/packages/wallet/src/features/transactions/transactionWatcherSaga.ts +++ b/packages/wallet/src/features/transactions/transactionWatcherSaga.ts @@ -2,9 +2,11 @@ import { ApolloClient, NormalizedCacheObject } from '@apollo/client' import { SwapEventName } from '@uniswap/analytics-events' import { TradeType } from '@uniswap/sdk-core' -import { BigNumberish, providers } from 'ethers' +import { BigNumber, BigNumberish, providers } from 'ethers' import { call, delay, fork, put, race, select, take, takeEvery } from 'typed-redux-saga' import { PollingInterval } from 'uniswap/src/constants/misc' +import { fetchSwaps } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' +import { SwapStatus } from 'uniswap/src/data/tradingApi/__generated__' import { FiatOnRampTransactionDetails } from 'uniswap/src/features/fiatOnRamp/types' import { findGasStrategyName } from 'uniswap/src/features/gas/hooks' import { getGasPrice } from 'uniswap/src/features/gas/types' @@ -20,7 +22,8 @@ import { updateTransaction, upsertFiatOnRampTransaction, } from 'uniswap/src/features/transactions/slice' -import { isClassic, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' +import { isBridge, isClassic, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' +import { toTradingApiSupportedChainId } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { BaseSwapTransactionInfo, FinalizedTransactionDetails, @@ -50,6 +53,16 @@ import { } from 'wallet/src/features/transactions/utils' import { getProvider } from 'wallet/src/features/wallet/context' +export const SWAP_STATUS_TO_TX_STATUS: { [key in SwapStatus]: TransactionStatus } = { + [SwapStatus.PENDING]: TransactionStatus.Pending, + [SwapStatus.SUCCESS]: TransactionStatus.Success, + [SwapStatus.NOT_FOUND]: TransactionStatus.Unknown, + [SwapStatus.FAILED]: TransactionStatus.Failed, + [SwapStatus.EXPIRED]: TransactionStatus.Expired, +} + +const FINALIZED_BRIDGE_SWAP_STATUS = [SwapStatus.SUCCESS, SwapStatus.FAILED, SwapStatus.EXPIRED] + export function* transactionWatcher({ apolloClient }: { apolloClient: ApolloClient }) { logger.debug('transactionWatcherSaga', 'transactionWatcher', 'Starting tx watcher') @@ -185,12 +198,13 @@ export function* watchTransaction({ logger.debug('transactionWatcherSaga', 'watchTransaction', 'Watching for updates for tx:', hash) const provider = yield* call(getProvider, chainId) - const nonce = isUniswapX(transaction) ? undefined : transaction.options.request.nonce - const { updatedTransaction, cancel, replace, invalidated } = yield* race({ + const options = isUniswapX(transaction) ? undefined : transaction.options + const { updatedTransaction, cancel, replace, invalidated, timeout } = yield* race({ updatedTransaction: call(waitForRemoteUpdate, transaction, provider), cancel: call(waitForCancellation, chainId, id), replace: call(waitForReplacement, chainId, id), - invalidated: call(waitForTxnInvalidated, chainId, id, nonce), + invalidated: call(waitForTxnInvalidated, chainId, id, options?.request.nonce), + ...(options?.timeoutTimestampMs ? { timeout: call(waitForTimeout, options.timeoutTimestampMs) } : {}), }) // `cancel` and `updatedTransaction` conditions apply to both Classic and UniswapX transactions @@ -240,6 +254,16 @@ export function* watchTransaction({ } return } + + if (timeout && transaction.status === TransactionStatus.Pending) { + logger.warn('transactionWatcherSaga', 'watchTransaction', 'Timeout for pending tx', { + hash, + chainId, + id, + }) + yield* call(maybeLogGasEstimateAccuracy, transaction) + return + } } export async function waitForReceipt( @@ -253,6 +277,16 @@ export async function waitForReceipt( return txReceipt } +function* waitForTimeout(timeoutTimestampMs: number) { + const currentTime = Date.now() + const delayTime = timeoutTimestampMs - currentTime + if (delayTime <= 0) { + return true + } + yield* delay(delayTime) + return true +} + function* waitForRemoteUpdate(transaction: TransactionDetails, provider: providers.Provider) { let hash = transaction.hash let status = transaction.status @@ -269,7 +303,7 @@ function* waitForRemoteUpdate(transaction: TransactionDetails, provider: provide } } - // At this point, the tx should either be a classic tx or a filled order, both of which have hashes + // At this point, the tx should either be a classic / bridge tx or a filled order, both of which have hashes if (!hash) { logger.error(new Error('Watching for tx with no hash'), { tags: { @@ -278,12 +312,22 @@ function* waitForRemoteUpdate(transaction: TransactionDetails, provider: provide }, extra: { transaction }, }) - return + return undefined } const ethersReceipt = yield* call(waitForReceipt, hash, provider) const receipt = receiptFromEthersReceipt(ethersReceipt) + if (isBridge(transaction)) { + // Bridge swaps become non-cancellable after the transaction is submitted to chain. + // Update should happen without watching to avoid an infinite re-watch loop. + const updatedTransaction = { ...transaction, cancellable: false } + yield* put(transactionActions.updateTransactionWithoutWatch(updatedTransaction)) + + // Poll for bridging status from BE + status = yield* call(waitForBridgingStatus, transaction) + } + // Classic transaction status is based on receipt, while UniswapX status is based backend response. if (isClassic(transaction)) { status = getFinalizedTransactionStatus(transaction.status, ethersReceipt?.status) @@ -291,6 +335,41 @@ function* waitForRemoteUpdate(transaction: TransactionDetails, provider: provide return { ...transaction, status, receipt, hash } } +function* waitForBridgingStatus(transaction: TransactionDetails) { + const txHash = transaction.hash + const chainId = toTradingApiSupportedChainId(transaction.chainId) + + if (!txHash || !chainId) { + return TransactionStatus.Unknown + } + + let swapStatus: SwapStatus | undefined + const initialPollIntervalMs = 500 + const maxRetries = 5 + const backoffFactor = 2 // Each retry will double the wait time + + let pollIndex = 0 + while (pollIndex < maxRetries) { + const currentPollInterval = initialPollIntervalMs * Math.pow(backoffFactor, pollIndex) + yield* delay(currentPollInterval) + + const data = yield* call(fetchSwaps, { + txHashes: [txHash], + chainId, + }) + + const currentSwapStatus = data.swaps?.[0]?.status + if (currentSwapStatus && FINALIZED_BRIDGE_SWAP_STATUS.includes(currentSwapStatus)) { + swapStatus = currentSwapStatus + break + } + + pollIndex++ + } + // If we didn't get a status after polling, assume it's failed + return swapStatus ? SWAP_STATUS_TO_TX_STATUS[swapStatus] : TransactionStatus.Failed +} + function* waitForCancellation(chainId: WalletChainId, id: string) { while (true) { const { payload } = yield* take>(cancelTransaction.type) @@ -432,11 +511,24 @@ export function logTransactionEvent(actionData: ReturnType transaction.options.timeoutTimestampMs + for (const estimate of [gasEstimates.activeEstimate, ...(gasEstimates.shadowEstimates || [])]) { const gasUseDiff = getDiff(estimate.gasLimit, transaction.receipt?.gasUsed) const gasPriceDiff = getDiff(getGasPrice(estimate), transaction.receipt?.effectiveGasPrice) @@ -455,9 +547,11 @@ function maybeLogGasEstimateAccuracy(transaction: FinalizedTransactionDetails) { gas_price_diff_percentage: getPercentageError(gasPriceDiff, getGasPrice(estimate)), gas_price: transaction.receipt?.effectiveGasPrice, max_priority_fee_per_gas: 'maxPriorityFeePerGas' in estimate ? estimate.maxPriorityFeePerGas : undefined, - private_rpc: isClassic(transaction) ? transaction.options.submitViaPrivateRpc : false, + out_of_gas, + private_rpc: isClassic(transaction) ? transaction.options.submitViaPrivateRpc ?? false : false, is_shadow: estimate !== gasEstimates.activeEstimate, name: findGasStrategyName(estimate), + timed_out, }) } } @@ -476,7 +570,7 @@ export function* finalizeTransaction({ // Refetch data when a local tx has confirmed yield* refetchGQLQueries({ transaction, apolloClient }) - if (transaction.typeInfo.type === TransactionType.Swap) { + if (transaction.typeInfo.type === TransactionType.Swap || transaction.typeInfo.type === TransactionType.Bridge) { const hasDoneOneSwap = (yield* select(selectSwapTransactionsCount)) === 1 if (hasDoneOneSwap) { // Only log event if it's a user's first ever swap diff --git a/packages/wallet/src/features/unitags/ClaimUnitagContent.tsx b/packages/wallet/src/features/unitags/ClaimUnitagContent.tsx index e8bddc88500..e1dfb039fc3 100644 --- a/packages/wallet/src/features/unitags/ClaimUnitagContent.tsx +++ b/packages/wallet/src/features/unitags/ClaimUnitagContent.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { ActivityIndicator } from 'react-native' import { useAnimatedStyle, useSharedValue, withDelay, withTiming } from 'react-native-reanimated' -import { AnimatePresence, Button, Flex, Text, TouchableArea, useSporeColors } from 'ui/src' +import { AnimatePresence, Button, Flex, FlexProps, Text, TouchableArea, useSporeColors } from 'ui/src' import { InfoCircleFilled } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { useDynamicFontSizing } from 'ui/src/hooks/useDynamicFontSizing' @@ -22,6 +22,7 @@ import { import { shortenAddress } from 'uniswap/src/utils/addresses' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { logger } from 'utilities/src/logger/logger' +import { isExtension } from 'utilities/src/platform' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { UnitagInfoModal } from 'wallet/src/features/unitags/UnitagInfoModal' import { UnitagName } from 'wallet/src/features/unitags/UnitagName' @@ -44,13 +45,17 @@ const UNITAG_NAME_ANIMATE_DISTANCE_Y = imageSizes.image100 + spacing.spacing48 + export function ClaimUnitagContent({ unitagAddress, entryPoint, + animateY = true, navigationEventConsumer, onNavigateContinue, + onComplete, }: { unitagAddress?: string entryPoint: UnitagEntryPoint + animateY?: boolean navigationEventConsumer?: EventConsumer - onNavigateContinue: (params: SharedUnitagScreenParams[UnitagScreens.ChooseProfilePicture]) => void // fix this any + onNavigateContinue?: (params: SharedUnitagScreenParams[UnitagScreens.ChooseProfilePicture]) => void + onComplete?: () => void }): JSX.Element { const { t } = useTranslation() const colors = useSporeColors() @@ -71,7 +76,7 @@ export function ClaimUnitagContent({ return { opacity: addressViewOpacity.value, } - }) + }, [addressViewOpacity]) const { error: canClaimUnitagNameError, loading: loadingUnitagErrorCheck } = useCanClaimUnitagName(unitagToCheck) @@ -138,7 +143,7 @@ export function ClaimUnitagContent({ const navigateWithAnimation = useCallback( (unitag: string) => { - if (!unitagAddress) { + if (entryPoint === OnboardingScreens.Landing && !unitagAddress) { const err = new Error('unitagAddress should always be defined') logger.error(err, { tags: { file: 'ClaimUnitagScreen', function: 'navigateWithAnimation' }, @@ -168,10 +173,21 @@ export function ClaimUnitagContent({ ) // Navigate to ChooseProfilePicture screen after initial delay + translation to allow animations to finish setTimeout(() => { - onNavigateContinue({ unitag, entryPoint, address: unitagAddress, unitagFontSize: fontSize }) + onComplete?.() + if (unitagAddress && onNavigateContinue) { + onNavigateContinue({ unitag, entryPoint, address: unitagAddress, unitagFontSize: fontSize }) + } }, initialDelay + translateYDuration) }, - [onNavigateContinue, addressViewOpacity, entryPoint, unitagAddress, unitagInputContainerTranslateY, fontSize], + [ + onComplete, + onNavigateContinue, + addressViewOpacity, + entryPoint, + unitagAddress, + unitagInputContainerTranslateY, + fontSize, + ], ) // Handle when useUnitagError completes loading and returns a result after onPressContinue is called @@ -197,6 +213,20 @@ export function ClaimUnitagContent({ } } + const extensionStyling: FlexProps = isExtension + ? { + backgroundColor: '$surface1', + borderRadius: '$rounded20', + borderWidth: 1, + borderColor: '$surface3', + py: '$spacing12', + px: '$spacing20', + mb: '$spacing20', + width: '100%', + justifyContent: 'space-between', + } + : {} + return ( <> {!showTextInputView && ( @@ -228,6 +259,7 @@ export function ClaimUnitagContent({ enterStyle={{ opacity: 0, x: 40 }} exitStyle={{ opacity: 0, x: 40 }} gap="$none" + {...extensionStyling} > @@ -284,15 +316,13 @@ export function ClaimUnitagContent({ - {canClaimUnitagNameError && unitagToCheck === unitagInputValue && ( - - - {canClaimUnitagNameError} - - - )} + + + {canClaimUnitagNameError} + + - +