From 156cbe72a4cb94863e7c8dcdb8d38cb1f663f7a7 Mon Sep 17 00:00:00 2001 From: Uniswap Labs Service Account Date: Wed, 4 Dec 2024 17:23:42 +0000 Subject: [PATCH] ci(release): publish latest release --- .depcheckrc | 1 + ...om-bottom-sheet-npm-4.5.1-d8ef5d483d.patch | 13 + ...ive-performance-npm-4.1.2-ec1fcc7507.patch | 11 + CODEOWNERS | 1 + RELEASE | 63 +- VERSION | 2 +- apps/extension/package.json | 39 +- apps/extension/src/app/OnboardingApp.tsx | 15 +- apps/extension/src/app/PopupApp.tsx | 19 +- apps/extension/src/app/SidebarApp.tsx | 21 +- apps/extension/src/app/UnitagClaimApp.tsx | 15 +- .../components/Trace/TraceUserProperties.tsx | 2 +- apps/extension/src/app/datadog.ts | 15 +- .../features/accounts/CreateWalletModal.tsx | 2 +- .../app/features/accounts/EditLabelModal.tsx | 2 +- .../AccountSwitcherScreen.test.tsx.snap | 120 +- .../dappRequests/DappRequestContent.tsx | 2 +- .../EthSend/Swap/SwapRequestContent.tsx | 2 +- .../requestContent/WrapContent.tsx | 2 +- .../features/home/PortfolioActionButtons.tsx | 2 +- .../src/app/features/home/PortfolioHeader.tsx | 3 +- .../app/features/home/SwitchNetworksModal.tsx | 2 +- .../app/features/home/TokenBalanceList.tsx | 2 +- .../__snapshots__/KeyboardKey.test.tsx.snap | 12 +- .../features/onboarding/create/NameWallet.tsx | 2 +- .../__snapshots__/ReceiveScreen.test.tsx.snap | 184 +- .../SettingsManageConnectionsScreen.tsx | 105 +- .../app/features/settings/SettingsScreen.tsx | 5 +- .../src/app/features/swap/SwapFlowScreen.tsx | 8 +- .../unitags/UnitagConfirmationScreen.tsx | 2 +- .../navigation/SideBarNavigationProvider.tsx | 2 +- apps/extension/src/manifest.json | 2 +- apps/extension/tsconfig.json | 2 +- apps/mobile/README.md | 14 + apps/mobile/android/app/build.gradle | 6 +- .../app/src/main/ic_launcher-playstore.png | Bin 0 -> 77585 bytes .../onboarding/import/SeedPhraseInput.kt | 15 +- .../import/SeedPhraseInputViewManager.kt | 12 +- .../import/SeedPhraseInputViewModel.kt | 15 + .../drawable-hdpi/ic_launcher_background.png | Bin 24608 -> 0 bytes .../drawable-hdpi/ic_launcher_foreground.png | Bin 49625 -> 0 bytes .../ic_stat_onesignal_default.png | Bin 5288 -> 2727 bytes .../main/res/drawable-hdpi/splash_logo.png | Bin 0 -> 44781 bytes .../drawable-mdpi/ic_launcher_background.png | Bin 14799 -> 0 bytes .../drawable-mdpi/ic_launcher_foreground.png | Bin 25890 -> 0 bytes .../ic_stat_onesignal_default.png | Bin 2396 -> 1484 bytes .../main/res/drawable-mdpi/splash_logo.png | Bin 0 -> 23890 bytes .../drawable-xhdpi/ic_launcher_background.png | Bin 40071 -> 0 bytes .../drawable-xhdpi/ic_launcher_foreground.png | Bin 77628 -> 0 bytes .../ic_stat_onesignal_default.png | Bin 9344 -> 4490 bytes .../main/res/drawable-xhdpi/splash_logo.png | Bin 0 -> 72416 bytes .../ic_launcher_background.png | Bin 72596 -> 0 bytes .../ic_launcher_foreground.png | Bin 146413 -> 0 bytes .../ic_stat_onesignal_default.png | Bin 20900 -> 8234 bytes .../main/res/drawable-xxhdpi/splash_logo.png | Bin 0 -> 139849 bytes .../ic_launcher_background.png | Bin 108711 -> 0 bytes .../ic_launcher_foreground.png | Bin 244564 -> 0 bytes .../ic_onesignal_large_icon_default.png | Bin 262892 -> 58627 bytes .../ic_stat_onesignal_default.png | Bin 37081 -> 12715 bytes .../main/res/drawable-xxxhdpi/splash_logo.png | Bin 0 -> 228034 bytes .../src/main/res/drawable/splashscreen.xml | 10 +- .../main/res/drawable/splashscreen_icon.xml | 20 +- .../src/main/res/drawable/uniswap_logo.xml | 35 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 7 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 5653 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 2186 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 3564 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 7019 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 4168 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2915 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1350 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 2028 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 3649 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2564 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 8961 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 3080 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 5598 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 11468 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 5996 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 17206 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 5108 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 11124 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 22986 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 10120 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 28961 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 7680 bytes .../ic_launcher_foreground.webp | Bin 0 -> 18814 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 38373 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 13682 bytes .../app/src/main/res/values-night/colors.xml | 2 +- .../res/values/ic_launcher_background.xml | 4 + apps/mobile/babel.config.js | 23 +- apps/mobile/ios/Podfile | 28 +- apps/mobile/ios/Podfile.lock | 1282 +- .../ios/Uniswap.xcodeproj/project.pbxproj | 205 +- apps/mobile/ios/Uniswap/AppDelegate.m | 3 - .../AppIcon.appiconset/100.png | Bin 0 -> 7912 bytes .../AppIcon.appiconset/1024.png | Bin 0 -> 256370 bytes .../AppIcon.appiconset/114.png | Bin 0 -> 9476 bytes .../AppIcon.appiconset/120.png | Bin 0 -> 10286 bytes .../AppIcon.appiconset/144.png | Bin 0 -> 13284 bytes .../AppIcon.appiconset/152.png | Bin 0 -> 14633 bytes .../AppIcon.appiconset/167.png | Bin 0 -> 16732 bytes .../AppIcon.appiconset/180.png | Bin 0 -> 18852 bytes .../Images.xcassets/AppIcon.appiconset/20.png | Bin 0 -> 699 bytes .../Images.xcassets/AppIcon.appiconset/29.png | Bin 0 -> 1140 bytes .../Images.xcassets/AppIcon.appiconset/40.png | Bin 0 -> 2083 bytes .../Images.xcassets/AppIcon.appiconset/50.png | Bin 0 -> 2816 bytes .../Images.xcassets/AppIcon.appiconset/57.png | Bin 0 -> 3399 bytes .../Images.xcassets/AppIcon.appiconset/58.png | Bin 0 -> 3530 bytes .../Images.xcassets/AppIcon.appiconset/60.png | Bin 0 -> 3647 bytes .../Images.xcassets/AppIcon.appiconset/72.png | Bin 0 -> 4826 bytes .../Images.xcassets/AppIcon.appiconset/76.png | Bin 0 -> 5236 bytes .../Images.xcassets/AppIcon.appiconset/80.png | Bin 0 -> 5596 bytes .../Images.xcassets/AppIcon.appiconset/87.png | Bin 0 -> 6324 bytes .../AppIcon.appiconset/AppIcon-1024@1x.png | Bin 612940 -> 0 bytes .../AppIcon.appiconset/AppIcon-20@2x.png | Bin 2503 -> 0 bytes .../AppIcon.appiconset/AppIcon-20@3x.png | Bin 4724 -> 0 bytes .../AppIcon.appiconset/AppIcon-29@1x.png | Bin 1545 -> 0 bytes .../AppIcon.appiconset/AppIcon-29@2x.png | Bin 4508 -> 0 bytes .../AppIcon.appiconset/AppIcon-29@3x.png | Bin 8651 -> 0 bytes .../AppIcon.appiconset/AppIcon-40@2x.png | Bin 7554 -> 0 bytes .../AppIcon.appiconset/AppIcon-40@3x.png | Bin 14730 -> 0 bytes .../AppIcon.appiconset/AppIcon-60@2x.png | Bin 14730 -> 0 bytes .../AppIcon.appiconset/AppIcon-60@3x.png | Bin 28934 -> 0 bytes .../AppIcon.appiconset/Contents.json | 74 +- .../AppIcon.appiconset/iPad-AppIcon-20@1x.png | Bin 905 -> 0 bytes .../AppIcon.appiconset/iPad-AppIcon-20@2x.png | Bin 2503 -> 0 bytes .../AppIcon.appiconset/iPad-AppIcon-29@1x.png | Bin 1545 -> 0 bytes .../AppIcon.appiconset/iPad-AppIcon-29@2x.png | Bin 4508 -> 0 bytes .../AppIcon.appiconset/iPad-AppIcon-40@1x.png | Bin 2503 -> 0 bytes .../iPad-AppIcon-40@2x-1.png | Bin 7554 -> 0 bytes .../AppIcon.appiconset/iPad-AppIcon-76@1x.png | Bin 6938 -> 0 bytes .../AppIcon.appiconset/iPad-AppIcon-76@2x.png | Bin 21625 -> 0 bytes .../iPad-AppIcon-83.5@2x.png | Bin 25461 -> 0 bytes .../SplashLogo.imageset/Contents.json | 23 + .../SplashLogo.imageset/SplashLogo 1.png | Bin 0 -> 185676 bytes .../SplashLogo.imageset/SplashLogo@2x 1.png | Bin 0 -> 615734 bytes .../SplashLogo.imageset/SplashLogo@3x 1.png | Bin 0 -> 1279660 bytes .../SplashScreen.imageset/Contents.json | 89 - .../SplashScreen.imageset/splash-dark.png | Bin 49172 -> 0 bytes .../SplashScreen.imageset/splash-dark@2x.png | Bin 165908 -> 0 bytes .../SplashScreen.imageset/splash-dark@3x.png | Bin 312309 -> 0 bytes .../SplashScreen.imageset/splash-light-1.png | Bin 46660 -> 0 bytes .../SplashScreen.imageset/splash-light.png | Bin 46660 -> 0 bytes .../splash-light@2x-1.png | Bin 144230 -> 0 bytes .../SplashScreen.imageset/splash-light@2x.png | Bin 144230 -> 0 bytes .../splash-light@3x-1.png | Bin 262219 -> 0 bytes .../SplashScreen.imageset/splash-light@3x.png | Bin 262219 -> 0 bytes .../Import/SeedPhraseInputManager.m | 2 + .../Import/SeedPhraseInputManager.swift | 21 +- .../Import/SeedPhraseInputView.swift | 129 +- .../Import/SeedPhraseInputViewModel.swift | 53 +- .../ios/Uniswap/SplashScreen.storyboard | 38 +- apps/mobile/jest-setup.js | 17 + apps/mobile/jest.config.js | 8 +- apps/mobile/package.json | 23 +- .../scripts/getFingerprintForRadonIDE.js | 5 + apps/mobile/scripts/podinstall.sh | 2 +- apps/mobile/src/app/App.tsx | 73 +- .../app/MobileWalletNavigationProvider.tsx | 31 +- .../src/app/modals/AccountSwitcherModal.tsx | 2 +- apps/mobile/src/app/modals/AppModals.tsx | 5 + apps/mobile/src/app/modals/SwapModal.tsx | 3 + .../src/app/modals/TokenWarningModalState.ts | 4 + .../app/modals/TokenWarningModalWrapper.tsx | 65 + .../AccountSwitcherModal.test.tsx.snap | 186 +- apps/mobile/src/app/navigation/NavBar.tsx | 26 +- apps/mobile/src/app/navigation/hooks.ts | 28 +- apps/mobile/src/app/navigation/navigation.tsx | 2 +- .../PriceExplorer/PriceExplorer.tsx | 195 +- .../PriceExplorerAnimatedNumber.tsx | 9 +- .../src/components/PriceExplorer/Text.tsx | 39 +- .../__snapshots__/Text.test.tsx.snap | 141 +- .../PriceExplorer/usePriceHistory.test.ts | 40 +- .../PriceExplorer/usePriceHistory.ts | 29 +- .../QRCodeScanner/QRCodeScanner.tsx | 41 +- .../RecipientSelect/RecipientScanModal.tsx | 32 +- .../RecipientSelect/RecipientSelect.tsx | 2 +- .../RemoveWallet/RemoveWalletModal.tsx | 2 - .../ConnectedDapps/ConnectedDappsList.tsx | 90 +- .../ConnectedDapps/DappConnectionItem.tsx | 78 +- .../components/Requests/DappHeaderIcon.tsx | 10 +- .../Requests/RequestModal/RequestDetails.tsx | 5 +- .../components/Requests/RequestModal/hooks.ts | 2 +- .../PendingConnectionSwitchNetworkModal.tsx | 2 +- .../ScanSheet/SwitchAccountOption.tsx | 3 +- .../Requests/ScanSheet/WalletConnectModal.tsx | 52 +- .../components/TokenDetails/LinkButton.tsx | 1 - .../components/TokenDetails/SendButton.tsx | 2 +- .../components/TokenDetails/TokenBalances.tsx | 4 +- .../TokenDetailsActionButtons.tsx | 18 +- .../TokenDetails/TokenDetailsContext.tsx | 131 + .../TokenDetailsFavoriteButton.tsx | 2 +- .../TokenDetails/TokenDetailsHeader.tsx | 44 +- .../TokenDetails/TokenDetailsLinks.tsx | 26 +- .../TokenDetails/TokenDetailsStats.tsx | 134 +- .../TokenDetails/useTokenDetailsColors.ts | 28 + .../useTokenDetailsCurrentChainBalance.ts | 19 + .../TokenSelector/TokenFiatOnRampList.tsx | 2 +- .../components/Trace/TraceUserProperties.tsx | 2 +- .../components/accounts/AccountCardItem.tsx | 5 +- .../accounts/AccountHeader.test.tsx | 3 +- .../src/components/accounts/AccountHeader.tsx | 19 +- .../components/accounts/AccountList.test.tsx | 3 +- .../AccountCardItem.test.tsx.snap | 70 +- .../__snapshots__/AccountHeader.test.tsx.snap | 89 +- .../__snapshots__/AccountList.test.tsx.snap | 77 +- .../src/components/buttons/BackButton.tsx | 2 +- .../__snapshots__/BackButton.test.tsx.snap | 20 +- .../__snapshots__/CloseButton.test.tsx.snap | 20 +- .../CopyTextButton.test.tsx.snap | 62 +- .../__snapshots__/LinkButton.test.tsx.snap | 36 +- .../components/explore/ExploreSections.tsx | 2 +- .../components/explore/FavoriteHeaderRow.tsx | 2 +- .../components/explore/FavoriteTokenCard.tsx | 14 +- .../explore/FavoriteWalletCard.test.tsx | 3 +- .../components/explore/FavoriteWalletCard.tsx | 4 +- .../src/components/explore/RemoveButton.tsx | 1 - .../components/explore/SortButton.test.tsx | 21 +- .../src/components/explore/SortButton.tsx | 2 +- .../src/components/explore/TokenItem.tsx | 10 +- .../FavoriteHeaderRow.test.tsx.snap | 47 +- .../FavoriteTokenCard.test.tsx.snap | 7 +- .../FavoriteWalletCard.test.tsx.snap | 83 +- .../__snapshots__/RemoveButton.test.tsx.snap | 33 +- .../__snapshots__/SortButton.test.tsx.snap | 34 +- .../__snapshots__/TokenItem.test.tsx.snap | 56 +- .../explore/search/SearchResultsSection.tsx | 2 +- .../src/components/explore/search/hooks.ts | 7 +- .../search/items/SearchENSAddressItem.tsx | 3 +- .../search/items/SearchEtherscanItem.tsx | 13 +- .../search/items/SearchNFTCollectionItem.tsx | 9 +- .../explore/search/items/SearchTokenItem.tsx | 4 +- .../explore/search/items/SearchUnitagItem.tsx | 3 +- .../items/SearchWalletByAddressItem.tsx | 3 +- .../search/items/SearchWalletItemBase.tsx | 6 +- .../src/components/fiatOnRamp/CtaButton.tsx | 1 + .../SelectionCircle.test.tsx.snap | 35 +- .../components/layout/SafeKeyboardScreen.tsx | 11 +- .../components/sortableGrid/SortableGrid.tsx | 8 +- .../contexts/DragContextProvider.tsx | 10 - .../internal/SortableGirdProvider.tsx | 2 - .../components/sortableGrid/internal/hooks.ts | 17 +- .../src/components/sortableGrid/types.ts | 1 - .../__snapshots__/DecimalNumber.test.tsx.snap | 35 +- .../TextWithFuseMatches.test.tsx.snap | 161 +- .../src/components/unitags/UnitagBanner.tsx | 2 +- .../src/features/appLoading/SplashScreen.tsx | 26 +- .../authentication/LockScreenModal.tsx | 2 - .../features/biometrics/useBiometricCheck.ts | 6 +- .../externalProfile/ProfileContextMenu.tsx | 9 +- .../externalProfile/ProfileHeader.tsx | 2 - ...xchangeTransferServiceProviderSelector.tsx | 4 +- .../fiatOnRamp/FiatOnRampAmountSection.tsx | 64 +- .../features/fiatOnRamp/FiatOnRampContext.tsx | 27 +- .../GenericImportForm.test.tsx.snap | 129 +- .../mobile/src/features/modals/ModalsState.ts | 3 + apps/mobile/src/features/modals/modalSlice.ts | 7 + .../collection/NFTCollectionContextMenu.tsx | 1 - .../nfts/item/CollectionPreviewCard.tsx | 2 +- .../CollectionPreviewCard.test.tsx.snap | 56 +- .../item/__snapshots__/traits.test.tsx.snap | 21 +- .../src/features/notifications/Onesignal.ts | 16 +- .../features/onboarding/OnboardingScreen.tsx | 15 +- .../src/features/onboarding/OptionCard.tsx | 3 - .../SafeKeyboardOnboardingScreen.tsx | 42 +- .../src/features/openai/OpenAIContext.tsx | 2 +- .../src/features/send/SendReviewScreen.tsx | 4 + .../unitags/UnitagConfirmationScreen.tsx | 5 +- .../src/features/walletConnect/utils.ts | 20 +- apps/mobile/src/screens/AppLoadingScreen.tsx | 2 +- apps/mobile/src/screens/ExploreScreen.tsx | 2 +- .../src/screens/FiatOnRampConnecting.tsx | 13 +- apps/mobile/src/screens/FiatOnRampScreen.tsx | 197 +- apps/mobile/src/screens/HomeScreen.tsx | 89 +- .../src/screens/Import/ImportMethodScreen.tsx | 1 - .../Import/RestoreCloudBackupScreen.tsx | 3 +- .../src/screens/Import/SeedPhraseInput.tsx | 90 - .../SeedPhraseInput/NativeSeedPhraseInput.ts | 4 + .../SeedPhraseInput/SeedPhraseInput.tsx | 79 + .../SeedPhraseInput/types.ts | 43 + .../SeedPhraseInputScreen.android.mock.tsx | 0 .../SeedPhraseInputScreen.tsx | 110 +- .../src/screens/Import/SelectWalletScreen.tsx | 35 +- .../src/screens/Import/WatchWalletScreen.tsx | 5 +- ...oreCloudBackupPasswordScreen.test.tsx.snap | 187 +- .../RestoreCloudBackupScreen.test.tsx.snap | 28 +- .../screens/Import/useOnDeviceRecoveryData.ts | 2 +- .../src/screens/NFTCollectionScreen.tsx | 11 +- apps/mobile/src/screens/NFTItemScreen.tsx | 14 +- .../src/screens/Onboarding/LandingScreen.tsx | 16 +- .../Onboarding/NotificationsSetupScreen.tsx | 8 +- .../__snapshots__/BackupScreen.test.tsx.snap | 442 +- .../mobile/src/screens/ReceiveCryptoModal.tsx | 8 +- apps/mobile/src/screens/SettingsScreen.tsx | 18 +- apps/mobile/src/screens/SettingsWallet.tsx | 13 +- .../mobile/src/screens/SettingsWalletEdit.tsx | 1 - .../src/screens/TokenDetailsHeaders.tsx | 124 +- .../mobile/src/screens/TokenDetailsScreen.tsx | 562 +- apps/mobile/src/test/render.tsx | 33 +- .../src/utils/haptics/useHapticFeedback.ts | 57 + apps/mobile/src/utils/hooks.ts | 31 +- apps/mobile/src/utils/splashScreen.ts | 6 +- .../src/utils/useIsScreenNavigationReady.ts | 24 + .../src/utils/useOpenBackupReminderModal.ts | 13 +- apps/mobile/src/utils/useSkeletonLoading.ts | 15 - apps/mobile/tsconfig.json | 2 +- apps/web/craco.config.cjs | 3 +- apps/web/cypress/e2e/buy-crypto-form.test.ts | 3 + .../mini-portfolio/activity-history.test.ts | 2 +- apps/web/cypress/e2e/navigation.test.ts | 7 +- apps/web/cypress/e2e/nfts.test.ts | 1 + apps/web/cypress/e2e/redirects.test.ts | 47 +- apps/web/cypress/e2e/swap/swap.test.ts | 2 +- apps/web/cypress/e2e/swap/uniswapx.test.ts | 2 +- apps/web/cypress/e2e/swap/uniswapxv2.test.ts | 2 +- apps/web/cypress/e2e/token-details.test.ts | 81 +- apps/web/cypress/e2e/token-explore.test.ts | 12 + .../e2e/wallet-connection/connect.test.ts | 2 +- .../tokens/__snapshots__/token.test.ts.snap | 4 +- apps/web/package.json | 56 +- apps/web/public/app-sitemap.xml | 12 - apps/web/public/index.html | 2 +- apps/web/public/nfts-sitemap.xml | 313 +- apps/web/public/pools-sitemap.xml | 4472 +- apps/web/public/tokens-sitemap.xml | 6217 +- apps/web/src/assets/images/arrow-right.svg | 3 - apps/web/src/assets/svg/search.svg | 2 +- .../AccountDetails/TransactionSummary.tsx | 7 +- .../AccountDrawer/AuthenticatedHeader.tsx | 2 +- .../components/AccountDrawer/LanguageMenu.tsx | 5 +- .../AccountDrawer/LocalCurrencyMenu.tsx | 4 +- .../Activity/CancelOrdersDialog.tsx | 10 +- .../Activity/OffchainActivityModal.tsx | 9 +- .../CancelOrdersDialog.test.tsx.snap | 956 +- .../MiniPortfolio/Activity/parseLocal.test.ts | 2 +- .../MiniPortfolio/Activity/parseLocal.ts | 14 +- .../MiniPortfolio/Activity/parseRemote.tsx | 46 +- .../MiniPortfolio/ExtensionDeeplinks.tsx | 13 +- .../MiniPortfolio/Limits/LimitsMenu.tsx | 8 +- .../LimitDetailActivityRow.test.tsx.snap | 2 +- .../OpenLimitOrdersButton.test.tsx.snap | 2 +- .../MiniPortfolio/NFTs/NFTItem.tsx | 2 +- .../MiniPortfolio/NFTs/NFTTab.tsx | 2 +- .../MiniPortfolio/Pools/PoolsTab.tsx | 230 +- .../MiniPortfolio/Pools/hooks.ts | 30 +- .../Pools/useMultiChainPositions.tsx | 2 +- .../MiniPortfolio/Tokens/TokensTab.tsx | 2 +- .../AccountDrawer/MiniPortfolio/constants.tsx | 16 +- .../components/AccountDrawer/SettingsMenu.tsx | 9 +- .../AccountDrawer/SmallBalanceToggle.tsx | 2 +- .../AccountDrawer/TestnetsToggle.tsx | 2 +- .../AccountDrawer/UniwalletModal.tsx | 23 +- .../__snapshots__/index.test.tsx.snap | 25 +- .../src/components/AccountDrawer/shared.tsx | 2 +- apps/web/src/components/AddressQRModal.tsx | 10 +- .../AnimatedDropdown/index.test.tsx | 30 - .../src/components/AnimatedDropdown/index.tsx | 38 - .../components/Banner/Outage/OutageBanner.tsx | 2 +- .../components/ChainConnectivityWarning.tsx | 33 +- .../ActiveLiquidityChart2.tsx | 139 + .../Charts/ActiveLiquidityChart/AxisLeft.tsx | 82 + .../Charts/ActiveLiquidityChart/Brush2.tsx | 266 + .../ActiveLiquidityChart/HorizontalArea.tsx | 57 + .../ActiveLiquidityChart/HorizontalLine.tsx | 36 + .../Charts/BandsIndicator/bands-indicator.ts | 160 + .../BandsIndicator/helpers/closest-index.ts | 48 + .../helpers/min-max-in-range.ts | 62 + .../BandsIndicator/helpers/simple-clone.ts | 7 + .../Charts/BandsIndicator/plugin-base.ts | 60 + apps/web/src/components/Charts/ChartModel.tsx | 11 +- .../Charts/LiquidityChart/index.tsx | 24 +- .../Charts/LiquidityChart/renderer.tsx | 9 +- .../LiquidityPositionRangeChart.tsx | 515 + .../LiquidityRangeInput.tsx | 310 + .../src/components/Charts/LoadingState.tsx | 3 +- .../RoundedCandlestickSeries/renderer.ts | 23 +- .../stacked-area-series/renderer.ts | 14 +- .../CrosshairHighlightPrimitive.tsx | 6 +- .../VolumeChart/CustomVolumeChartModel.tsx | 2 +- .../Charts/VolumeChart/renderer.tsx | 5 +- apps/web/src/components/Charts/hooks.ts | 4 +- apps/web/src/components/Charts/utils.tsx | 18 + .../src/components/ConfirmSwapModal/Modal.tsx | 62 +- .../__snapshots__/Head.test.tsx.snap | 8 +- .../src/components/ConfirmSwapModal/index.tsx | 5 +- .../components/ConnectedAccountBlocked.tsx | 7 +- .../LimitPriceInputPanel.test.tsx | 62 +- .../LimitPriceInputPanel.test.tsx.snap | 10 +- .../SwapCurrencyInputPanel.tsx | 6 +- .../components/CurrencyInputPanel/index.tsx | 2 +- apps/web/src/components/Dialog/Dialog.tsx | 65 +- .../Dialog/__snapshots__/Dialog.test.tsx.snap | 810 +- apps/web/src/components/Expand/index.test.tsx | 2 +- apps/web/src/components/Expand/index.tsx | 13 +- .../FeatureFlagModal/FeatureFlagModal.tsx | 36 +- apps/web/src/components/FeeSelector/index.tsx | 4 +- .../src/components/FiatOnrampModal/index.tsx | 6 +- .../src/components/Icons/PrivacyOptions.tsx | 19 + .../IncreaseLiquidityContext.tsx | 4 +- .../IncreaseLiquidityReview.tsx | 49 +- .../IncreaseLiquidityTxContext.tsx | 39 +- .../components/IncreaseLiquidity/hooks.tsx | 13 +- .../Liquidity/FeeTierSearchModal.tsx | 39 +- .../src/components/Liquidity/HookModal.tsx | 32 +- .../Liquidity/LiquidityModalDetailRows.tsx | 63 +- .../Liquidity/LiquidityModalHeader.test.tsx | 11 +- .../Liquidity/LiquidityModalHeader.tsx | 16 +- .../LiquidityPositionAmountsTile.tsx | 63 +- .../Liquidity/LiquidityPositionCard.tsx | 371 +- .../Liquidity/LiquidityPositionFeeStats.tsx | 178 +- .../Liquidity/LiquidityPositionInfo.test.tsx | 6 + .../Liquidity/LiquidityPositionInfo.tsx | 41 +- .../Liquidity/LiquidityPositionInfoBadges.tsx | 2 +- .../LiquidityPositionPriceRangeTile.tsx | 1 + .../LiquidityPositionStatusIndicator.tsx | 10 + .../web/src/components/Liquidity/analytics.ts | 73 + apps/web/src/components/Liquidity/hooks.ts | 22 +- apps/web/src/components/Liquidity/types.ts | 12 +- apps/web/src/components/Liquidity/utils.tsx | 46 +- .../LiquidityChartRangeInput/Chart.tsx | 2 +- .../LiquidityChartRangeInput/hooks.ts | 43 +- .../LiquidityChartRangeInput/index.tsx | 33 +- .../LiquidityChartRangeInput/svg.tsx | 33 + .../LiquidityChartRangeInput/types.ts | 1 + apps/web/src/components/Logo/ChainLogo.tsx | 2 +- apps/web/src/components/Modal/index.tsx | 200 - .../ChainSelector/ChainSelectorRow.test.tsx | 44 - .../NavBar/ChainSelector/ChainSelectorRow.tsx | 103 - .../ChainSelectorRow.test.tsx.snap | 1097 - .../components/NavBar/ChainSelector/index.tsx | 11 +- .../NavBar/CompanyMenu/MenuDropdown.tsx | 5 + .../NavBar/CompanyMenu/MobileMenuDrawer.tsx | 2 +- .../NavBar/DownloadApp/Modal/index.tsx | 18 +- .../NewUserCTAButton.test.tsx.snap | 4 +- .../NavBar/LegalAndPrivacyMenu/index.tsx | 63 + .../NavBar/MobileBottomBar/TDPActionTabs.tsx | 5 +- .../NavBar/NavDropdown/NavDropdown.tsx | 18 +- .../components/NavBar/NavDropdown/shared.tsx | 3 +- .../NavBar/PreferencesMenu/Preferences.tsx | 9 +- .../NavBar/PreferencesMenu/index.tsx | 2 +- .../SearchBar/RecentlySearchedAssets.ts | 33 +- .../NavBar/SearchBar/SearchBar.test.tsx | 10 - .../SearchBar/SearchBarDropdown.test.tsx | 14 +- .../NavBar/SearchBar/SearchBarDropdown.tsx | 195 +- .../NavBar/SearchBar/SuggestionRow.tsx | 9 +- .../__snapshots__/SearchBar.test.tsx.snap | 184 +- .../SearchBarDropdown.test.tsx.snap | 566 +- .../src/components/NavBar/SearchBar/index.tsx | 15 +- .../components/NavBar/Tabs/TabsContent.tsx | 23 +- apps/web/src/components/NavBar/index.tsx | 18 +- apps/web/src/components/NumericalInput.tsx | 6 +- apps/web/src/components/PercentInput.tsx | 6 +- .../PoolProgressIndicator.tsx | 14 +- .../Pools/PoolDetails/ChartSection/hooks.ts | 55 +- .../Pools/PoolDetails/ChartSection/index.tsx | 39 +- .../Pools/PoolDetails/PoolDetailsHeader.tsx | 24 +- .../Pools/PoolDetails/PoolDetailsLink.tsx | 2 +- .../PoolDetailsPositionTable.test.tsx | 46 +- .../PoolDetails/PoolDetailsPositionsTable.tsx | 121 +- .../Pools/PoolDetails/PoolDetailsStats.tsx | 5 +- .../PoolDetails/PoolDetailsStatsButtons.tsx | 206 +- .../Pools/PoolDetails/PoolDetailsTable.tsx | 27 +- .../PoolDetailsTransactionsTable.tsx | 4 +- .../PoolDetailsHeader.test.tsx.snap | 16 +- .../PoolDetailsPositionTable.test.tsx.snap | 400 +- .../PoolDetailsStatsButtons.test.tsx.snap | 593 +- .../PoolDetailsTransactionTable.test.tsx.snap | 278 +- .../Pools/PoolTable/PoolTable.test.tsx | 16 +- .../components/Pools/PoolTable/PoolTable.tsx | 90 +- .../__snapshots__/PoolTable.test.tsx.snap | 408 +- .../src/components/Popups/PopupContent.tsx | 2 +- apps/web/src/components/Popups/PopupItem.tsx | 2 +- .../components/Popups/ToastRegularSimple.tsx | 16 +- .../web/src/components/PositionCard/index.tsx | 4 +- apps/web/src/components/PositionPreview.tsx | 2 +- apps/web/src/components/PrivacyChoices.tsx | 95 + apps/web/src/components/PrivacyPolicy.tsx | 6 +- .../ReceiveCryptoModal/ChooseProvider.tsx | 13 +- .../RemoveLiquidityModalContext.tsx | 4 +- .../RemoveLiquidity/RemoveLiquidityReview.tsx | 79 +- .../RemoveLiquidityTxContext.tsx | 16 +- .../src/components/RemoveLiquidity/hooks.ts | 35 +- .../UniswapXRouterLabel.test.tsx.snap | 2 +- .../components/SearchModal/CurrencySearch.tsx | 18 +- .../web/src/components/SearchModal/styled.tsx | 7 - .../MaxSlippageSettings/index.test.tsx | 2 +- .../Settings/MultipleRoutingOptions.tsx | 8 +- .../index.test.tsx | 2 +- .../src/components/Settings/index.test.tsx | 7 +- apps/web/src/components/Settings/index.tsx | 12 +- apps/web/src/components/SwapBottomCard.tsx | 6 +- apps/web/src/components/SwitchLocaleLink.tsx | 5 +- .../Table/__snapshots__/styled.test.tsx.snap | 12 +- apps/web/src/components/Table/styled.tsx | 6 +- .../TokenSafety/TokenSafetyModal.tsx | 6 +- apps/web/src/components/TokenSafety/index.tsx | 6 +- .../Tokens/TokenDetails/BalanceSummary.tsx | 2 +- .../TokenDetails/InvalidTokenDetails.tsx | 2 +- .../Tokens/TokenDetails/Skeleton.tsx | 9 +- .../Tokens/TokenDetails/StatsSection.tsx | 2 +- .../Tokens/TokenDetails/TokenDescription.tsx | 13 +- .../__snapshots__/Skeleton.test.tsx.snap | 10 +- .../TokenDescription.test.tsx.snap | 92 +- .../components/Tokens/TokenDetails/index.tsx | 18 +- .../tables/TokenDetailsPoolsTable.test.tsx | 5 +- .../TokenDetails/tables/TransactionsTable.tsx | 4 +- .../TokenDetailsPoolsTable.test.tsx.snap | 356 +- .../Tokens/TokenTable/NetworkFilter.tsx | 68 +- .../Tokens/TokenTable/SearchBar.tsx | 36 +- .../components/Tokens/TokenTable/index.tsx | 2 +- .../components/TopLevelModals/LaunchModal.tsx | 13 +- .../TopLevelModals/UkDisclaimerModal.tsx | 6 +- .../TopLevelModals/UnichainLaunchModal.tsx | 26 - .../src/components/TopLevelModals/index.tsx | 10 +- .../TransactionConfirmationModal/index.tsx | 8 +- .../WalletModal/ConnectionErrorView.tsx | 6 +- .../WalletModal/UniswapWalletOptions.tsx | 19 +- .../UniswapWalletOptions.test.tsx.snap | 16 +- apps/web/src/components/WalletModal/index.tsx | 2 +- .../useOrderedConnections.test.tsx | 18 +- .../WalletModal/useOrderedConnections.tsx | 32 +- .../Web3Provider/WebUniswapContext.tsx | 20 +- .../web/src/components/Web3Provider/index.tsx | 9 +- .../components/Web3Provider/wagmiConfig.ts | 3 +- .../components/Web3Provider/walletConnect.ts | 4 +- .../components/claim/AddressClaimModal.tsx | 7 +- apps/web/src/components/deprecated/Box.ts | 6 - .../components/swap/GasBreakdownTooltip.tsx | 3 +- .../components/swap/GasEstimateTooltip.tsx | 4 +- .../src/components/swap/PriceImpactModal.tsx | 7 +- apps/web/src/components/swap/SwapDetails.tsx | 16 +- .../components/swap/SwapDetailsDropdown.tsx | 6 +- .../src/components/swap/SwapHeader.test.tsx | 46 +- apps/web/src/components/swap/SwapHeader.tsx | 4 +- .../swap/UnsupportedCurrencyFooter.test.tsx | 3 +- .../swap/UnsupportedCurrencyFooter.tsx | 10 +- .../__snapshots__/SwapDetails.test.tsx.snap | 20 +- .../SwapDetailsDropdown.test.tsx.snap | 7 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 79 +- .../UnsupportedCurrencyFooter.test.tsx.snap | 15 +- apps/web/src/constants/chains.test.ts | 59 - apps/web/src/constants/routing.test.ts | 4 +- .../useFeatureFlagUrlOverrides.tsx | 3 +- .../src/graphql/data/RecentTokenTransfers.ts | 2 +- apps/web/src/graphql/data/TrendingTokens.ts | 2 +- .../data/apollo/AssetActivityProvider.tsx | 2 +- .../data/apollo/TokenBalancesProvider.tsx | 78 +- apps/web/src/graphql/data/nft/Collection.ts | 5 +- .../src/graphql/data/nft/CollectionSearch.ts | 56 - .../web/src/graphql/data/pools/usePoolData.ts | 56 +- .../graphql/data/pools/usePoolTransactions.ts | 77 +- .../data/pools/usePoolsFromTokenAddress.ts | 95 +- .../web/src/graphql/data/pools/useTopPools.ts | 12 +- .../src/graphql/data/useTokenTransactions.ts | 2 +- apps/web/src/graphql/data/util.tsx | 6 +- apps/web/src/hooks/Tokens.ts | 3 +- apps/web/src/hooks/useAccount.ts | 4 +- apps/web/src/hooks/useActiveLocalCurrency.ts | 35 - apps/web/src/hooks/useActiveLocale.ts | 67 - .../web/src/hooks/useAutoSlippageTolerance.ts | 2 +- apps/web/src/hooks/useConfirmModalState.ts | 4 +- apps/web/src/hooks/useFeeTierDistribution.ts | 2 +- .../useFilterPossiblyMaliciousPositions.ts | 2 +- apps/web/src/hooks/useGlobalChainSwitch.ts | 2 +- apps/web/src/hooks/useIsBuyPage.ts | 6 + apps/web/src/hooks/useIsMigrateV3Page.ts | 5 + apps/web/src/hooks/useIsPoolOutOfSync.ts | 21 +- apps/web/src/hooks/useIsPositionsPage.ts | 5 + .../src/hooks/useLocalCurrencyLinkProps.ts | 7 +- apps/web/src/hooks/useLocationLinkProps.ts | 3 +- apps/web/src/hooks/useParsedQueryString.ts | 13 - apps/web/src/hooks/usePoolPriceChartData.tsx | 62 + apps/web/src/hooks/usePoolTickData.ts | 261 +- apps/web/src/hooks/usePools.ts | 43 +- apps/web/src/hooks/useSendCallback.ts | 2 +- .../hooks/useStablecoinAmountFromFiatValue.ts | 31 + apps/web/src/hooks/useStablecoinPrice.ts | 99 - apps/web/src/hooks/useSwapCallback.tsx | 6 +- apps/web/src/hooks/useSwitchChain.ts | 2 +- apps/web/src/hooks/useTokenAllowance.ts | 4 +- apps/web/src/hooks/useTokenBalances.ts | 2 +- apps/web/src/hooks/useTransactionDeadline.ts | 4 +- apps/web/src/hooks/useUSDPrice.ts | 55 +- apps/web/src/hooks/useUSDTokenUpdater.ts | 15 +- apps/web/src/hooks/useUniswapXSwapCallback.ts | 27 +- apps/web/src/hooks/useUniversalRouter.ts | 6 +- apps/web/src/hooks/useWrapCallback.tsx | 6 +- apps/web/src/i18n/LanguageProvider.tsx | 17 +- apps/web/src/index.tsx | 72 +- apps/web/src/lib/hooks/useCallContext.ts | 4 +- apps/web/src/lib/utils/analytics.ts | 4 + apps/web/src/lib/utils/searchBar.ts | 37 +- apps/web/src/nft/components/bag/BagFooter.tsx | 12 +- .../components/collection/CollectionNfts.tsx | 6 +- .../explore/TrendingCollections.tsx | 6 +- .../nft/components/profile/list/ListPage.tsx | 4 +- .../profile/list/Modal/ListModal.tsx | 4 +- .../profile/list/Modal/SuccessScreen.tsx | 4 +- .../components/profile/view/FilterSidebar.tsx | 20 +- .../components/profile/view/ProfilePage.tsx | 19 +- apps/web/src/nft/hooks/useBagTotalEthPrice.ts | 4 +- apps/web/src/pages/AddLiquidity/redirects.tsx | 34 - apps/web/src/pages/AddLiquidityV2/index.tsx | 38 +- .../src/pages/AddLiquidityV2/redirects.tsx | 15 +- .../Review.tsx | 0 .../blastAlerts.tsx | 0 .../index.tsx | 111 +- .../src/pages/AddLiquidityV3/redirects.tsx | 53 + .../styled.tsx | 0 apps/web/src/pages/App/Layout.tsx | 1 + .../Explore/charts/ExploreChartsSection.tsx | 20 +- apps/web/src/pages/Explore/index.tsx | 42 +- .../Explore/tables/RecentTransactions.tsx | 4 +- .../IncreaseLiquidityForm.tsx | 18 +- .../IncreaseLiquidityModal.tsx | 62 +- .../web/src/pages/Landing/sections/Footer.tsx | 2 +- apps/web/src/pages/LegacyPool/CTACards.tsx | 2 +- .../web/src/pages/LegacyPool/PositionPage.tsx | 36 +- apps/web/src/pages/LegacyPool/index.test.tsx | 8 +- apps/web/src/pages/LegacyPool/index.tsx | 2 +- apps/web/src/pages/LegacyPool/redirects.tsx | 18 +- apps/web/src/pages/LegacyPool/shared.tsx | 4 +- .../web/src/pages/MigrateV2/MigrateV2Pair.tsx | 132 +- apps/web/src/pages/MigrateV2/index.tsx | 27 +- .../MigrateV3/MigrateV3LiquidityTxContext.tsx | 30 +- apps/web/src/pages/MigrateV3/index.tsx | 135 +- .../pages/Pool/Positions/ClaimFeeModal.tsx | 88 +- .../src/pages/Pool/Positions/PositionPage.tsx | 321 +- .../pages/Pool/Positions/PositionsHeader.tsx | 241 +- .../web/src/pages/Pool/Positions/TopPools.tsx | 107 + .../pages/Pool/Positions/V2PositionPage.tsx | 286 +- .../pages/Pool/Positions/create/AddHook.tsx | 6 +- .../Positions/create/BaseQuoteFiatAmount.tsx | 41 + .../Positions/create/ContextProviders.tsx | 108 +- .../Pool/Positions/create/CreatePosition.tsx | 374 +- .../create/CreatePositionContext.tsx | 29 +- .../Positions/create/CreatePositionModal.tsx | 168 +- .../pages/Pool/Positions/create/Deposit.tsx | 75 +- .../create/DynamicFeeTierSpeedbump.tsx | 82 + .../pages/Pool/Positions/create/EditStep.tsx | 120 +- .../Positions/create/PoolOutOfSyncError.tsx | 39 + .../Positions/create/RangeSelectionStep.tsx | 141 +- .../create/ResetCreatePositionsFormModal.tsx | 84 + .../Pool/Positions/create/SelectTokenStep.tsx | 281 +- .../Pool/Positions/create/TradingAPIError.tsx | 33 + .../src/pages/Pool/Positions/create/hooks.tsx | 185 +- .../pages/Pool/Positions/create/shared.tsx | 49 +- .../src/pages/Pool/Positions/create/types.ts | 11 + .../src/pages/Pool/Positions/create/utils.tsx | 113 +- apps/web/src/pages/Pool/Positions/index.tsx | 279 - apps/web/src/pages/Pool/Positions/shared.tsx | 46 +- apps/web/src/pages/Pool/index.tsx | 369 +- apps/web/src/pages/PoolDetails/index.test.tsx | 9 - apps/web/src/pages/PoolDetails/index.tsx | 16 +- .../RemoveLiquidity/RemoveLiquidityForm.tsx | 14 +- .../RemoveLiquidity/RemoveLiquidityModal.tsx | 33 +- .../RemoveLiquidity/{index.tsx => V2.tsx} | 70 +- apps/web/src/pages/RemoveLiquidity/V3.tsx | 59 +- apps/web/src/pages/RouteDefinitions.tsx | 17 +- apps/web/src/pages/Swap/Buy/BuyForm.tsx | 29 +- .../web/src/pages/Swap/Buy/BuyFormContext.tsx | 7 +- .../pages/Swap/Buy/ChooseProviderModal.tsx | 2 +- .../src/pages/Swap/Buy/CountryListModal.tsx | 32 +- .../CountryListModal.test.tsx.snap | 4 +- .../CountryListRow.test.tsx.snap | 16 +- .../PredefinedAmount.test.tsx.snap | 16 +- .../ProviderConnectedView.test.tsx.snap | 20 +- .../ProviderConnectionError.test.tsx.snap | 20 +- apps/web/src/pages/Swap/Buy/hooks.ts | 17 +- apps/web/src/pages/Swap/Limit/LimitForm.tsx | 12 +- .../Swap/Send/SendCurrencyInputForm.test.tsx | 50 +- .../pages/Swap/Send/SendCurrencyInputForm.tsx | 25 +- apps/web/src/pages/Swap/Send/SendForm.tsx | 14 - .../Swap/Send/SendRecipientForm.test.tsx | 62 +- .../src/pages/Swap/Send/SendRecipientForm.tsx | 2 +- .../pages/Swap/Send/SendReviewModal.test.tsx | 38 +- .../src/pages/Swap/Send/SendReviewModal.tsx | 17 +- .../NewAddressSpeedBump.test.tsx.snap | 420 +- .../SendCurrencyInputForm.test.tsx.snap | 24 +- .../SendRecipientForm.test.tsx.snap | 4 +- .../SendReviewModal.test.tsx.snap | 38 +- .../SmartContractSpeedbump.test.tsx.snap | 398 +- apps/web/src/pages/Swap/SwapForm.tsx | 64 +- apps/web/src/pages/Swap/index.tsx | 139 +- .../pages/__snapshots__/routes.test.ts.snap | 12 +- apps/web/src/setupTests.ts | 25 + apps/web/src/state/activity/polling/orders.ts | 2 +- apps/web/src/state/application/reducer.ts | 12 +- apps/web/src/state/application/updater.ts | 2 +- apps/web/src/state/explore/protocolStats.ts | 24 +- apps/web/src/state/explore/topPools.ts | 40 +- apps/web/src/state/explore/types.ts | 1 + apps/web/src/state/mint/v3/hooks.tsx | 119 +- .../state/multichain/MultichainContext.tsx | 39 + apps/web/src/state/multichain/types.ts | 26 + .../state/multichain/useMultichainContext.tsx | 16 + .../state/sagas/liquidity/liquiditySaga.ts | 62 +- apps/web/src/state/sagas/root.ts | 3 +- .../src/state/sagas/transactions/swapSaga.ts | 7 +- .../src/state/sagas/transactions/uniswapx.ts | 2 + .../web/src/state/sagas/transactions/utils.ts | 1 + .../state/sagas/transactions/watcherSaga.ts | 45 + apps/web/src/state/sagas/utils/transaction.ts | 167 + apps/web/src/state/send/SendContext.tsx | 2 +- apps/web/src/state/send/hooks.tsx | 4 +- apps/web/src/state/swap/SwapContext.test.tsx | 46 +- apps/web/src/state/swap/SwapContext.tsx | 69 +- .../swap/{hooks.test.ts => hooks.test.tsx} | 45 +- apps/web/src/state/swap/hooks.tsx | 14 +- apps/web/src/state/swap/types.ts | 21 - apps/web/src/state/swap/useSwapContext.tsx | 11 +- apps/web/src/state/transactions/hooks.tsx | 10 +- apps/web/src/state/transactions/types.ts | 24 +- apps/web/src/state/user/hooks.tsx | 3 +- apps/web/src/test-utils/bundle-size-test.ts | 4 +- apps/web/src/test-utils/pools/fixtures.ts | 2 +- apps/web/src/test-utils/render.tsx | 22 +- apps/web/src/theme/components/index.tsx | 2 +- apps/web/src/utils/chainParams.test.ts | 24 + .../src/utils/computeSurroundingTicks.test.ts | 3 +- apps/web/src/utils/computeSurroundingTicks.ts | 12 +- apps/web/src/utils/formatNumbers.test.ts | 46 +- apps/web/src/utils/formatNumbers.ts | 142 +- apps/web/src/utils/urlRoutes.ts | 5 + config/jest-presets/jest/jest-preset.js | 2 +- config/jest-presets/jest/setup.js | 8 + config/jest-presets/package.json | 2 +- config/tsconfig/ui.json | 4 +- dangerfile.ts | 46 + eslint-local-rules.js | 6 + package.json | 94 +- packages/eslint-config/README.md | 29 + .../__snapshots__/preset.test.ts.snap | 48 + packages/eslint-config/base.js | 3 +- packages/eslint-config/native.js | 179 +- packages/eslint-config/package.json | 28 +- .../eslint-config/plugins/custom-map-sort.js | 56 + .../plugins/custom-map-sort.test.js | 52 + .../eslint-config/plugins/no-unwrapped-t.js | 97 + .../plugins/no-unwrapped-t.test.js | 91 + packages/eslint-config/react.js | 3 +- packages/ui/jest-setup.js | 16 + packages/ui/jest.config.js | 48 +- packages/ui/package.json | 37 +- .../animations/components/AnimateInOrder.tsx | 17 +- .../animations/components/HeightAnimator.tsx | 35 +- .../ui/src/animations/components/Jiggly.tsx | 11 - .../animations/components/WidthAnimator.tsx | 13 +- packages/ui/src/animations/index.ts | 2 +- packages/ui/src/animations/layout/index.ts | 2 + .../layout/layoutAnimation.native.ts | 27 +- .../src/animations/layout/layoutAnimation.ts | 3 +- .../animations/layout/layoutAnimation.web.ts | 3 +- packages/ui/src/animations/layout/types.ts | 6 + .../layout/useLayoutAnimationOnChange.ts | 13 + packages/ui/src/assets/icons/arrow-right.svg | 3 + .../ui/src/assets/icons/arrows-left-right.svg | 5 + .../assets/icons/horizontal-density-chart.svg | 3 + .../src/assets/icons/loading-price-curve.svg | 23 + packages/ui/src/assets/icons/minus.svg | 3 + packages/ui/src/assets/icons/search-minus.svg | 3 + packages/ui/src/assets/icons/search-plus.svg | 3 + packages/ui/src/assets/index.ts | 1 + .../logos/png/uniswap-mono-logo-large.png | Bin 0 -> 374538 bytes .../src/components/InlineCard/InlineCard.tsx | 4 +- .../ui/src/components/QRCode/QRCode.test.tsx | 42 + packages/ui/src/components/QRCode/QRCode.tsx | 43 +- .../src/components/QRCode/QRCodeDisplay.tsx | 6 +- .../QRCode/__snapshots__/QRCode.test.tsx.snap | 50257 ++++++++++++++++ packages/ui/src/components/button/Button.tsx | 18 +- .../src/components/button/PlusMinusButton.tsx | 1 - .../components/checkbox/LabeledCheckbox.tsx | 18 +- .../ui/src/components/icons/ArrowRight.tsx | 17 + .../src/components/icons/ArrowsLeftRight.tsx | 20 + .../icons/HorizontalDensityChart.tsx | 17 + .../components/icons/LoadingPriceCurve.tsx | 45 + packages/ui/src/components/icons/Minus.tsx | 16 + .../ui/src/components/icons/SearchMinus.tsx | 18 + .../ui/src/components/icons/SearchPlus.tsx | 18 + packages/ui/src/components/icons/exported.ts | 7 + .../src/components/modal/AdaptiveWebModal.tsx | 39 +- packages/ui/src/components/text/Text.tsx | 1 + .../components/touchable/TouchableArea.tsx | 20 +- .../ui/src/components/touchable/types.tsx | 3 - packages/ui/src/hooks/constants.ts | 8 + .../hooks/useIsShortMobileDevice.native.ts | 17 +- .../ui/src/hooks/useIsShortMobileDevice.ts | 7 +- .../src/hooks/useIsShortMobileDevice.web.ts | 8 +- packages/ui/src/hooks/useSporeColors.ts | 13 +- packages/ui/src/index.ts | 8 +- packages/ui/src/loading/Shine.tsx | 5 +- ...remove-declaration-files-from-utilities.ts | 35 + packages/ui/src/test/render.tsx | 13 + packages/ui/src/theme/color/colors.ts | 2 + packages/ui/src/theme/fonts.ts | 2 +- packages/ui/src/theme/iconSizes.ts | 1 + packages/ui/src/theme/shadows.ts | 43 +- packages/ui/src/theme/spacing.ts | 2 + packages/ui/src/utils/haptics/helpers.ts | 32 - .../utils/haptics/useHapticFeedback.native.ts | 44 - .../ui/src/utils/haptics/useHapticFeedback.ts | 9 - packages/ui/tsconfig.json | 2 +- packages/uniswap/babel.config.js | 23 +- packages/uniswap/jest-setup.js | 9 + packages/uniswap/jest.config.js | 1 + packages/uniswap/package.json | 26 +- .../src/components/BaseCard/BaseCard.tsx | 6 +- .../ConfirmSwapModal/steps/Swap.tsx | 2 +- .../CurrencyInputPanel/CurrencyInputPanel.tsx | 11 +- .../CurrencyInputPanel/MaxAmountButton.tsx | 1 - .../CurrencyInputPanel/SelectTokenButton.tsx | 1 - .../components/CurrencyLogo/CurrencyLogo.tsx | 2 +- .../__snapshots__/CurrencyLogo.test.tsx.snap | 10 +- .../__snapshots__/SplitLogo.test.tsx.snap | 10 +- .../__snapshots__/TokenLogo.test.tsx.snap | 10 +- .../InlineWarningCard/InlineWarningCard.tsx | 14 + .../TokenSelector/TokenSelector.tsx | 18 +- .../TokenSelector/TokenSelectorList.tsx | 12 +- .../src/components/TokenSelector/constants.ts | 2 + .../TokenSelector/flowToModalName.tsx | 13 - .../components/TokenSelector/hooks.test.ts | 39 +- .../src/components/TokenSelector/hooks.tsx | 673 - .../hooks/useAddToSearchHistory.ts | 43 + .../hooks/useAllCommonBaseCurrencies.ts | 26 + .../hooks/useCommonTokensOptions.ts | 52 + .../useCommonTokensOptionsWithFallback.ts | 23 + .../TokenSelector/hooks/useCurrencies.ts | 34 + .../hooks/useCurrencyInfosToTokenOptions.ts | 78 + .../hooks/useFavoriteCurrencies.ts | 32 + .../hooks/useFavoriteTokensOptions.ts | 53 + .../TokenSelector/hooks/useFilterCallbacks.ts | 94 + .../hooks/usePopularTokensOptions.ts | 46 + .../usePortfolioBalancesForAddressById.ts | 24 + .../hooks/usePortfolioTokenOptions.ts | 38 + .../hooks/useRecentlySearchedTokens.ts | 63 + .../hooks/useTokenSectionsForEmptySearch.tsx | 57 + .../hooks/useTokenSectionsForSearchResults.ts | 107 + .../{ => items}/SuggestedToken.tsx | 4 +- .../TokenSelector/{ => items}/TokenCard.tsx | 4 +- .../{ => items}/TokenOptionItem.tsx | 6 +- .../{ => items}/TokenSectionHeader.tsx | 0 .../HorizontalTokenList.native.tsx | 4 +- .../HorizontalTokenList.tsx | 0 .../HorizontalTokenList.web.tsx | 4 +- .../TokenSectionBaseList.native.tsx | 2 +- .../TokenSectionBaseList.tsx | 2 +- .../TokenSectionBaseList.web.tsx | 4 +- .../TokenSelectorEmptySearchList.tsx | 2 +- .../TokenSelectorSearchResultsList.tsx | 5 +- .../{ => lists}/TokenSelectorSendList.tsx | 4 +- .../TokenSelectorSwapInputList.tsx | 14 +- .../TokenSelectorSwapOutputList.tsx | 14 +- .../src/components/TokenSelector/utils.tsx | 19 +- .../components/banners/TestnetModeBanner.tsx | 5 +- .../src/components/buttons/PasteButton.tsx | 6 +- .../dropdowns/ActionSheetDropdown.tsx | 66 +- .../src/components/input/TextInput.tsx | 16 +- .../components/modals/ActionSheetModal.tsx | 4 +- .../components/modals/HandleBar.native.tsx | 9 +- .../src/components/modals/HandleBar.tsx | 2 + .../src/components/modals/HandleBar.web.tsx | 6 + .../src/components/modals/InfoLinkModal.tsx | 8 +- .../src/components/modals/Modal.native.tsx | 11 +- .../src/components/modals/Modal.web.tsx | 13 +- .../src/components/modals/ModalProps.tsx | 8 + .../modals/WarningModal/WarningModal.tsx | 6 +- .../modals/WarningModal/getAlertColor.ts | 6 +- .../components/modals/WarningModal/types.ts | 18 + .../components/network/NetworkFilter.test.tsx | 16 +- .../src/components/network/NetworkFilter.tsx | 9 +- .../src/components/network/NetworkOption.tsx | 13 +- .../uniswap/src/components/network/hooks.tsx | 12 +- .../src/components/text/LearnMoreLink.tsx | 18 +- .../uniswap/src/components/warnings/utils.ts | 6 +- packages/uniswap/src/constants/routing.ts | 29 +- packages/uniswap/src/constants/tokens.ts | 20 +- .../uniswap/src/constants/transactions.ts | 2 +- packages/uniswap/src/constants/urls.ts | 8 +- packages/uniswap/src/contexts/UrlContext.tsx | 43 + .../useCreateLpPositionCalldataQuery.ts | 16 +- .../useDecreaseLpPositionCalldataQuery.ts | 16 +- .../useIncreaseLpPositionCalldataQuery.ts | 16 +- .../utils/getTradeSettingsDeadline.ts | 7 + .../src/data/balances/hooks/useBalances.ts | 5 +- .../balances/hooks/useCrossChainBalances.ts | 4 + packages/uniswap/src/data/balances/utils.tsx | 2 +- .../graphql/uniswap-data-api/fragments.ts | 122 + .../graphql/uniswap-data-api/queries.graphql | 335 +- .../data/graphql/uniswap-data-api/queries.ts | 1 + .../uniswap-data-api/web/allV3Ticks.graphql | 1 + .../uniswap-data-api/web/allV4Ticks.graphql | 11 + .../uniswap-data-api/web/topPools.graphql | 33 +- packages/uniswap/src/data/rest/getPair.ts | 5 + packages/uniswap/src/data/tradingApi/api.json | 2 +- .../src/features/address/ExplorerView.tsx | 76 - .../src/features/address/TokenAddressView.tsx | 75 + .../src/features/bridging/hooks/tokens.ts | 3 +- .../uniswap/src/features/chains/chainInfo.ts | 2 +- .../{hooks.ts => hooks/useEnabledChains.ts} | 79 +- .../chains/hooks/useFeatureFlaggedChainIds.ts | 12 + .../features/chains/hooks/useNewChainIds.ts | 9 + .../chains/hooks/useOrderedChainIds.ts | 19 + .../chains/hooks/useSupportedChainId.ts | 33 + packages/uniswap/src/features/chains/types.ts | 9 +- packages/uniswap/src/features/chains/utils.ts | 8 +- .../uniswap/src/features/dataApi/balances.ts | 3 +- .../src/features/dataApi/searchTokens.test.ts | 14 +- .../src/features/dataApi/searchTokens.ts | 2 +- .../uniswap/src/features/dataApi/utils.ts | 6 +- .../uniswap/src/features/ens/constants.ts | 1 + packages/uniswap/src/features/ens/useENS.ts | 3 +- .../src/features/fiatCurrency/hooks.ts | 23 +- .../fiatOnRamp/FiatOnRampCountryPicker.tsx | 1 - .../features/fiatOnRamp/SelectTokenButton.tsx | 35 +- .../TokenSelectorBalanceDisplay.tsx | 2 +- .../uniswap/src/features/fiatOnRamp/hooks.ts | 12 +- packages/uniswap/src/features/gas/hooks.ts | 2 +- .../uniswap/src/features/gating/configs.ts | 55 +- packages/uniswap/src/features/gating/flags.ts | 125 +- .../src/features/language/constants.ts | 4 + .../uniswap/src/features/language/hooks.tsx | 90 +- .../uniswap/src/features/language/saga.ts | 4 +- .../src/features/notifications/types.ts | 1 + .../providers/createEthersProvider.ts | 28 +- .../src/features/search/SearchResult.test.ts | 18 + .../src/features/search/SearchResult.ts | 9 +- .../src/features/search/SearchTextInput.tsx | 2 +- .../uniswap/src/features/settings/hooks.ts | 2 +- .../src/features/telemetry/constants/trace.ts | 17 +- .../features/telemetry/constants/wallet.ts | 1 + .../uniswap/src/features/telemetry/types.ts | 58 +- .../features/testnets/TestnetModeModal.tsx | 1 + .../src/features/tokens/TokenWarningCard.tsx | 46 +- .../tokens/TokenWarningFlagsTable.tsx | 127 + .../src/features/tokens/TokenWarningModal.tsx | 228 +- .../tokens/WarningInfoModalContainer.tsx | 14 + packages/uniswap/src/features/tokens/hooks.ts | 2 +- .../src/features/tokens/safetyUtils.test.ts | 230 +- .../src/features/tokens/safetyUtils.ts | 75 +- .../DecimalPadInput/DecimalPad.native.tsx | 6 +- .../BridgeTokenButton.tsx | 2 +- .../BuyNativeTokenButton.tsx | 2 +- .../useInsufficientNativeTokenWarning.tsx | 2 +- .../SwapReviewTokenWarningCard.tsx | 53 +- .../transactions/TransactionDetails/utils.ts | 15 +- .../TransactionModal.native.tsx | 5 +- .../TransactionModal/TransactionModal.web.tsx | 2 + .../TransactionModalContext.tsx | 4 + .../TransactionModalProps.tsx | 1 + .../hooks/useParsedTransactionWarnings.tsx | 26 +- .../features/transactions/liquidity/types.ts | 16 +- .../transactions/refetchGQLQueriesSaga.ts | 37 +- .../features/transactions/swap/SwapFlow.tsx | 75 +- .../features/transactions/swap/analytics.ts | 14 +- .../swap/contexts/SwapFormContext.tsx | 23 +- .../swap/form/SwapArrowButton.tsx | 1 - .../transactions/swap/form/SwapFormButton.tsx | 1 - .../transactions/swap/form/SwapFormHeader.tsx | 2 +- .../transactions/swap/form/SwapFormScreen.tsx | 45 +- .../swap/form/SwapFormSettings.tsx | 80 +- .../swap/form/SwapTokenSelector.tsx | 35 +- .../form/footer/GasAndWarningRows.native.tsx | 2 +- .../form/footer/GasAndWarningRows.web.tsx | 70 +- .../swap/form/footer/GasTradeRow.tsx | 53 +- .../swap/form/footer/TradeWarningRow.tsx | 12 + .../transactions/swap/form/utils.test.ts | 73 + .../features/transactions/swap/form/utils.ts | 17 + .../swap/hooks/useDerivedSwapInfo.ts | 18 +- .../swap/hooks/useSetTradeSlippage.ts | 16 +- .../swap/hooks/useSwapPrefilledState.ts | 7 +- .../swap/hooks/useSwapWarningUtils.ts | 64 - .../swap/hooks/useSwapWarnings.tsx | 79 +- .../transactions/swap/hooks/useTrade.ts | 12 +- .../swap/hooks/useTransactionRequestInfo.ts | 19 +- .../swap/modals/FeeOnTransferWarning.tsx | 30 +- .../swap/modals/MarketPriceImpactWarning.tsx | 10 +- .../swap/modals/NetworkFeeWarning.tsx | 14 +- .../swap/modals/PriceImpactWarning.tsx | 27 - .../transactions/swap/modals/RoutingInfo.tsx | 166 +- .../swap/modals/SwapFeeWarning.tsx | 6 +- .../swap/modals/SwapWarningModal.tsx | 2 +- .../swap/review/SubmitSwapButton.tsx | 2 +- .../transactions/swap/review/SwapDetails.tsx | 31 + .../swap/review/SwapErrorScreen.tsx | 6 +- .../swap/review/SwapReviewScreen.tsx | 99 +- .../swap/review/TransactionAmountsReview.tsx | 26 +- .../swap/settings/SwapSettingsModal.tsx | 98 +- .../settings/configs/ProtocolPreference.tsx | 22 +- .../swap/settings/configs/Slippage.native.tsx | 5 +- .../swap/settings/configs/Slippage.web.tsx | 29 +- .../settings/contexts/SwapSettingsContext.tsx | 79 + .../swap/settings/useDeadlineSettings.ts | 17 +- .../swap/settings/useSlippageSettings.ts | 26 +- .../swap/types/derivedSwapInfo.ts | 3 - .../features/transactions/swap/types/trade.ts | 6 +- .../transactions/types/transactionDetails.ts | 22 +- .../src/features/unitags/constants.ts | 1 + .../src/i18n/locales/source/en-US.json | 104 +- .../src/i18n/locales/translations/af-ZA.json | 89 +- .../src/i18n/locales/translations/ar-SA.json | 89 +- .../src/i18n/locales/translations/ca-ES.json | 89 +- .../src/i18n/locales/translations/cs-CZ.json | 89 +- .../src/i18n/locales/translations/da-DK.json | 87 +- .../src/i18n/locales/translations/de-DE.json | 87 +- .../src/i18n/locales/translations/el-GR.json | 89 +- .../src/i18n/locales/translations/es-ES.json | 2261 +- .../src/i18n/locales/translations/fi-FI.json | 91 +- .../src/i18n/locales/translations/fil-PH.json | 2312 + .../src/i18n/locales/translations/fr-FR.json | 2133 +- .../src/i18n/locales/translations/he-IL.json | 87 +- .../src/i18n/locales/translations/hi-IN.json | 89 +- .../src/i18n/locales/translations/hu-HU.json | 91 +- .../src/i18n/locales/translations/id-ID.json | 91 +- .../src/i18n/locales/translations/it-IT.json | 89 +- .../src/i18n/locales/translations/ja-JP.json | 2047 +- .../src/i18n/locales/translations/ko-KR.json | 123 +- .../src/i18n/locales/translations/ms-MY.json | 91 +- .../src/i18n/locales/translations/nl-NL.json | 1893 +- .../src/i18n/locales/translations/no-NO.json | 89 +- .../src/i18n/locales/translations/pl-PL.json | 89 +- .../src/i18n/locales/translations/pt-BR.json | 860 +- .../src/i18n/locales/translations/pt-PT.json | 3199 +- .../src/i18n/locales/translations/ro-RO.json | 89 +- .../src/i18n/locales/translations/ru-RU.json | 2311 +- .../src/i18n/locales/translations/sl-SI.json | 89 +- .../src/i18n/locales/translations/sr-SP.json | 91 +- .../src/i18n/locales/translations/sv-SE.json | 87 +- .../src/i18n/locales/translations/sw-TZ.json | 91 +- .../src/i18n/locales/translations/tr-TR.json | 1601 +- .../src/i18n/locales/translations/uk-UA.json | 85 +- .../src/i18n/locales/translations/ur-PK.json | 91 +- .../src/i18n/locales/translations/vi-VN.json | 367 +- .../src/i18n/locales/translations/zh-CN.json | 405 +- .../src/i18n/locales/translations/zh-TW.json | 329 +- packages/uniswap/src/test/fixtures/events.ts | 6 +- packages/uniswap/src/utils/addresses.test.ts | 3 + packages/uniswap/src/utils/addresses.ts | 53 +- packages/uniswap/tsconfig.json | 2 +- packages/utilities/package.json | 4 +- .../utilities/src/addresses/addresses.test.ts | 47 +- packages/utilities/src/addresses/index.ts | 20 +- .../src/react/useBooleanState.test.ts | 66 + .../utilities/src/react/useBooleanState.ts | 17 + .../src/react/useHasValueChanged.test.ts | 38 + .../utilities/src/react/useHasValueChanged.ts | 7 + .../react/usePreviousWithLayoutEffect.test.ts | 37 + .../src/react/usePreviousWithLayoutEffect.ts | 11 + packages/utilities/src/test/fixtures.ts | 2 + packages/utilities/tsconfig.json | 2 +- packages/wallet/babel.config.js | 22 +- packages/wallet/jest.config.js | 1 + packages/wallet/package.json | 8 +- .../LogoWithTxStatus.test.tsx.snap | 14 +- .../ErrorBoundary/ErrorBoundary.tsx | 9 +- .../components/QRCodeScanner/WalletQRCode.tsx | 9 +- .../RecipientSearch/RecipientList.tsx | 7 +- .../RecipientSelectSpeedBumps.tsx | 2 +- .../WalletConnect/DappIconPlaceholder.tsx | 3 +- .../components/accounts/AccountDetails.tsx | 2 +- .../components/accounts/AddressDisplay.tsx | 12 +- .../accounts/AnimatedUnitagDisplayName.tsx | 10 +- .../components/accounts/DisplayNameText.tsx | 2 +- .../components/banners/InformationBanner.tsx | 8 +- .../introCards/useSharedIntroCards.ts | 2 +- .../src/components/nfts/NFTHiddenRow.tsx | 10 +- .../src/components/nfts/NftView.native.tsx | 4 +- .../nfts/NftViewWithContextMenu.web.tsx | 2 +- .../wallet/src/components/nfts/NftsList.tsx | 8 +- .../DappEllipsisDropdown.native.tsx | 47 + .../DappEllipsisDropdown.tsx | 13 + .../DappEllipsisDropdown.web.tsx | 35 +- .../internal/DappEllipsisDropdownIcon.tsx | 10 + .../src/contexts/WalletNavigationContext.tsx | 4 +- .../wallet/src/features/accounts/hooks.ts | 2 +- .../wallet/src/features/activity/hooks.ts | 17 +- .../src/features/activity/useActivityData.tsx | 7 +- .../components/NotificationToast.tsx | 10 +- .../SharedNotificationToastRouter.tsx | 1 + .../src/features/notifications/utils.ts | 3 +- .../hooks/useImportableAccounts.tsx | 2 +- .../src/features/portfolio/AnimatedNumber.tsx | 31 +- .../features/portfolio/HiddenTokensRow.tsx | 10 +- .../features/portfolio/TokenBalanceItem.tsx | 6 +- .../portfolio/TokenBalanceListContext.tsx | 2 +- .../portfolio/useTokenContextMenu.tsx | 2 +- .../wallet/src/features/telemetry/hooks.ts | 2 +- .../wallet/src/features/testnetMode/hooks.ts | 2 +- .../DetailsModal/BridgeTransactionDetails.tsx | 2 +- .../TransactionDetailsInfoRows.tsx | 2 +- .../TransactionDetailsModal.test.tsx | 7 +- .../DetailsModal/TransactionDetailsModal.tsx | 20 +- .../TransactionDetailsModal.test.tsx.snap | 10 +- .../SummaryItems/CancelConfirmationView.tsx | 6 +- .../SummaryItems/TransferTokenSummaryItem.tsx | 2 +- .../SummaryItems/UnknownSummaryItem.tsx | 3 +- .../TransactionHistoryUpdater.tsx | 10 +- .../transactions/contexts/SendContext.tsx | 5 +- .../wallet/src/features/transactions/hooks.ts | 10 +- .../transactions/send/SendAmountInput.tsx | 5 +- .../transactions/send/SendReviewDetails.tsx | 11 +- .../send/hooks/useDerivedSendInfo.ts | 2 +- .../send/hooks/useSendTransactionRequest.ts | 2 +- .../transactions/sendTransactionSaga.test.ts | 114 +- .../transactions/sendTransactionSaga.ts | 159 +- .../transactions/swap/WalletSwapFlow.tsx | 38 +- .../swap/createSwapFormFromTxDetails.ts | 7 +- .../swap/hooks/useSwapCallback.ts | 5 +- .../transactions/transactionWatcherSaga.ts | 41 +- .../src/features/unitags/AvatarSelection.ts | 2 +- .../features/unitags/ChangeUnitagModal.tsx | 2 +- .../features/unitags/ClaimUnitagContent.tsx | 4 +- .../unitags/EditUnitagProfileContent.tsx | 2 +- .../src/features/unitags/UnitagInfoModal.tsx | 4 +- .../unitags/UnitagWithProfilePicture.tsx | 2 +- packages/wallet/src/features/unitags/hooks.ts | 2 +- packages/wallet/src/features/unitags/utils.ts | 2 +- packages/wallet/src/features/wallet/hooks.ts | 5 +- packages/wallet/tsconfig.json | 2 +- scripts/local-version-check.sh | 2 +- yarn.lock | 5438 +- 1123 files changed, 98066 insertions(+), 28673 deletions(-) create mode 100644 .yarn/patches/@gorhom-bottom-sheet-npm-4.5.1-d8ef5d483d.patch create mode 100644 .yarn/patches/@shopify-react-native-performance-npm-4.1.2-ec1fcc7507.patch create mode 100644 CODEOWNERS create mode 100644 apps/mobile/android/app/src/main/ic_launcher-playstore.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-hdpi/splash_logo.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-mdpi/splash_logo.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-xhdpi/splash_logo.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-xxhdpi/splash_logo.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png delete mode 100644 apps/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png create mode 100644 apps/mobile/android/app/src/main/res/drawable-xxxhdpi/splash_logo.png create mode 100644 apps/mobile/android/app/src/main/res/drawable/uniswap_logo.xml create mode 100644 apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp delete mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/100.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/1024.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/114.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/120.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/144.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/152.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/167.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/180.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/20.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/29.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/40.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/50.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/57.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/58.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/60.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/72.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/76.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/80.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/87.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-1024@1x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-20@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-20@3x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-29@1x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-29@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-29@3x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-40@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-40@3x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-60@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/AppIcon-60@3x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-20@1x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-20@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-29@1x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-29@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-40@1x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-40@2x-1.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-76@1x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-76@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/AppIcon.appiconset/iPad-AppIcon-83.5@2x.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashLogo.imageset/Contents.json create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashLogo.imageset/SplashLogo 1.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashLogo.imageset/SplashLogo@2x 1.png create mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashLogo.imageset/SplashLogo@3x 1.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/Contents.json delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-dark.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-dark@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-dark@3x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-light-1.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-light.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-light@2x-1.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-light@2x.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-light@3x-1.png delete mode 100644 apps/mobile/ios/Uniswap/Images.xcassets/SplashScreen.imageset/splash-light@3x.png create mode 100644 apps/mobile/scripts/getFingerprintForRadonIDE.js create mode 100644 apps/mobile/src/app/modals/TokenWarningModalState.ts create mode 100644 apps/mobile/src/app/modals/TokenWarningModalWrapper.tsx create mode 100644 apps/mobile/src/components/TokenDetails/TokenDetailsContext.tsx create mode 100644 apps/mobile/src/components/TokenDetails/useTokenDetailsColors.ts create mode 100644 apps/mobile/src/components/TokenDetails/useTokenDetailsCurrentChainBalance.ts delete mode 100644 apps/mobile/src/screens/Import/SeedPhraseInput.tsx create mode 100644 apps/mobile/src/screens/Import/SeedPhraseInputScreen/SeedPhraseInput/NativeSeedPhraseInput.ts create mode 100644 apps/mobile/src/screens/Import/SeedPhraseInputScreen/SeedPhraseInput/SeedPhraseInput.tsx create mode 100644 apps/mobile/src/screens/Import/SeedPhraseInputScreen/SeedPhraseInput/types.ts rename apps/mobile/src/screens/Import/{ => SeedPhraseInputScreen}/SeedPhraseInputScreen.android.mock.tsx (100%) rename apps/mobile/src/screens/Import/{ => SeedPhraseInputScreen}/SeedPhraseInputScreen.tsx (62%) create mode 100644 apps/mobile/src/utils/haptics/useHapticFeedback.ts create mode 100644 apps/mobile/src/utils/useIsScreenNavigationReady.ts delete mode 100644 apps/mobile/src/utils/useSkeletonLoading.ts delete mode 100644 apps/web/src/assets/images/arrow-right.svg delete mode 100644 apps/web/src/components/AnimatedDropdown/index.test.tsx delete mode 100644 apps/web/src/components/AnimatedDropdown/index.tsx create mode 100644 apps/web/src/components/Charts/ActiveLiquidityChart/ActiveLiquidityChart2.tsx create mode 100644 apps/web/src/components/Charts/ActiveLiquidityChart/AxisLeft.tsx create mode 100644 apps/web/src/components/Charts/ActiveLiquidityChart/Brush2.tsx create mode 100644 apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalArea.tsx create mode 100644 apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalLine.tsx create mode 100644 apps/web/src/components/Charts/BandsIndicator/bands-indicator.ts create mode 100644 apps/web/src/components/Charts/BandsIndicator/helpers/closest-index.ts create mode 100644 apps/web/src/components/Charts/BandsIndicator/helpers/min-max-in-range.ts create mode 100644 apps/web/src/components/Charts/BandsIndicator/helpers/simple-clone.ts create mode 100644 apps/web/src/components/Charts/BandsIndicator/plugin-base.ts create mode 100644 apps/web/src/components/Charts/LiquidityPositionRangeChart/LiquidityPositionRangeChart.tsx create mode 100644 apps/web/src/components/Charts/LiquidityRangeInput/LiquidityRangeInput.tsx create mode 100644 apps/web/src/components/Icons/PrivacyOptions.tsx create mode 100644 apps/web/src/components/Liquidity/analytics.ts delete mode 100644 apps/web/src/components/Modal/index.tsx delete mode 100644 apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.test.tsx delete mode 100644 apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.tsx delete mode 100644 apps/web/src/components/NavBar/ChainSelector/__snapshots__/ChainSelectorRow.test.tsx.snap create mode 100644 apps/web/src/components/NavBar/LegalAndPrivacyMenu/index.tsx create mode 100644 apps/web/src/components/PrivacyChoices.tsx delete mode 100644 apps/web/src/components/TopLevelModals/UnichainLaunchModal.tsx delete mode 100644 apps/web/src/constants/chains.test.ts delete mode 100644 apps/web/src/graphql/data/nft/CollectionSearch.ts delete mode 100644 apps/web/src/hooks/useActiveLocalCurrency.ts delete mode 100644 apps/web/src/hooks/useActiveLocale.ts create mode 100644 apps/web/src/hooks/useIsBuyPage.ts create mode 100644 apps/web/src/hooks/useIsMigrateV3Page.ts create mode 100644 apps/web/src/hooks/useIsPositionsPage.ts delete mode 100644 apps/web/src/hooks/useParsedQueryString.ts create mode 100644 apps/web/src/hooks/usePoolPriceChartData.tsx create mode 100644 apps/web/src/hooks/useStablecoinAmountFromFiatValue.ts delete mode 100644 apps/web/src/hooks/useStablecoinPrice.ts delete mode 100644 apps/web/src/pages/AddLiquidity/redirects.tsx rename apps/web/src/pages/{AddLiquidity => AddLiquidityV3}/Review.tsx (100%) rename apps/web/src/pages/{AddLiquidity => AddLiquidityV3}/blastAlerts.tsx (100%) rename apps/web/src/pages/{AddLiquidity => AddLiquidityV3}/index.tsx (95%) create mode 100644 apps/web/src/pages/AddLiquidityV3/redirects.tsx rename apps/web/src/pages/{AddLiquidity => AddLiquidityV3}/styled.tsx (100%) create mode 100644 apps/web/src/pages/Pool/Positions/TopPools.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/BaseQuoteFiatAmount.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/DynamicFeeTierSpeedbump.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/PoolOutOfSyncError.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/ResetCreatePositionsFormModal.tsx create mode 100644 apps/web/src/pages/Pool/Positions/create/TradingAPIError.tsx delete mode 100644 apps/web/src/pages/Pool/Positions/index.tsx rename apps/web/src/pages/RemoveLiquidity/{index.tsx => V2.tsx} (92%) create mode 100644 apps/web/src/state/multichain/MultichainContext.tsx create mode 100644 apps/web/src/state/multichain/types.ts create mode 100644 apps/web/src/state/multichain/useMultichainContext.tsx create mode 100644 apps/web/src/state/sagas/transactions/watcherSaga.ts create mode 100644 apps/web/src/state/sagas/utils/transaction.ts rename apps/web/src/state/swap/{hooks.test.ts => hooks.test.tsx} (85%) create mode 100644 apps/web/src/utils/chainParams.test.ts create mode 100644 eslint-local-rules.js create mode 100644 packages/eslint-config/README.md create mode 100644 packages/eslint-config/plugins/custom-map-sort.js create mode 100644 packages/eslint-config/plugins/custom-map-sort.test.js create mode 100644 packages/eslint-config/plugins/no-unwrapped-t.js create mode 100644 packages/eslint-config/plugins/no-unwrapped-t.test.js create mode 100644 packages/ui/jest-setup.js create mode 100644 packages/ui/src/animations/layout/index.ts create mode 100644 packages/ui/src/animations/layout/types.ts create mode 100644 packages/ui/src/animations/layout/useLayoutAnimationOnChange.ts create mode 100644 packages/ui/src/assets/icons/arrow-right.svg create mode 100644 packages/ui/src/assets/icons/arrows-left-right.svg create mode 100644 packages/ui/src/assets/icons/horizontal-density-chart.svg create mode 100644 packages/ui/src/assets/icons/loading-price-curve.svg create mode 100644 packages/ui/src/assets/icons/minus.svg create mode 100644 packages/ui/src/assets/icons/search-minus.svg create mode 100644 packages/ui/src/assets/icons/search-plus.svg create mode 100644 packages/ui/src/assets/logos/png/uniswap-mono-logo-large.png create mode 100644 packages/ui/src/components/QRCode/QRCode.test.tsx create mode 100644 packages/ui/src/components/QRCode/__snapshots__/QRCode.test.tsx.snap create mode 100644 packages/ui/src/components/icons/ArrowRight.tsx create mode 100644 packages/ui/src/components/icons/ArrowsLeftRight.tsx create mode 100644 packages/ui/src/components/icons/HorizontalDensityChart.tsx create mode 100644 packages/ui/src/components/icons/LoadingPriceCurve.tsx create mode 100644 packages/ui/src/components/icons/Minus.tsx create mode 100644 packages/ui/src/components/icons/SearchMinus.tsx create mode 100644 packages/ui/src/components/icons/SearchPlus.tsx create mode 100644 packages/ui/src/scripts/remove-declaration-files-from-utilities.ts create mode 100644 packages/ui/src/test/render.tsx delete mode 100644 packages/ui/src/utils/haptics/helpers.ts delete mode 100644 packages/ui/src/utils/haptics/useHapticFeedback.native.ts delete mode 100644 packages/ui/src/utils/haptics/useHapticFeedback.ts create mode 100644 packages/uniswap/src/components/TokenSelector/constants.ts delete mode 100644 packages/uniswap/src/components/TokenSelector/flowToModalName.tsx delete mode 100644 packages/uniswap/src/components/TokenSelector/hooks.tsx create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useAddToSearchHistory.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useAllCommonBaseCurrencies.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useCommonTokensOptions.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useCommonTokensOptionsWithFallback.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useCurrencies.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useCurrencyInfosToTokenOptions.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useFavoriteCurrencies.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useFavoriteTokensOptions.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useFilterCallbacks.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/usePopularTokensOptions.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/usePortfolioBalancesForAddressById.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/usePortfolioTokenOptions.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useRecentlySearchedTokens.ts create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useTokenSectionsForEmptySearch.tsx create mode 100644 packages/uniswap/src/components/TokenSelector/hooks/useTokenSectionsForSearchResults.ts rename packages/uniswap/src/components/TokenSelector/{ => items}/SuggestedToken.tsx (91%) rename packages/uniswap/src/components/TokenSelector/{ => items}/TokenCard.tsx (90%) rename packages/uniswap/src/components/TokenSelector/{ => items}/TokenOptionItem.tsx (97%) rename packages/uniswap/src/components/TokenSelector/{ => items}/TokenSectionHeader.tsx (100%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/HorizontalTokenList/HorizontalTokenList.native.tsx (93%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/HorizontalTokenList/HorizontalTokenList.tsx (100%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/HorizontalTokenList/HorizontalTokenList.web.tsx (96%) rename packages/uniswap/src/components/TokenSelector/{ => lists/TokenSectionBaseList}/TokenSectionBaseList.native.tsx (95%) rename packages/uniswap/src/components/TokenSelector/{ => lists/TokenSectionBaseList}/TokenSectionBaseList.tsx (96%) rename packages/uniswap/src/components/TokenSelector/{ => lists/TokenSectionBaseList}/TokenSectionBaseList.web.tsx (97%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/TokenSelectorEmptySearchList.tsx (94%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/TokenSelectorSearchResultsList.tsx (91%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/TokenSelectorSendList.tsx (97%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/TokenSelectorSwapInputList.tsx (89%) rename packages/uniswap/src/components/TokenSelector/{ => lists}/TokenSelectorSwapOutputList.tsx (92%) create mode 100644 packages/uniswap/src/components/modals/HandleBar.web.tsx create mode 100644 packages/uniswap/src/contexts/UrlContext.tsx create mode 100644 packages/uniswap/src/data/apiClients/tradingApi/utils/getTradeSettingsDeadline.ts create mode 100644 packages/uniswap/src/data/graphql/uniswap-data-api/fragments.ts create mode 100644 packages/uniswap/src/data/graphql/uniswap-data-api/web/allV4Ticks.graphql delete mode 100644 packages/uniswap/src/features/address/ExplorerView.tsx create mode 100644 packages/uniswap/src/features/address/TokenAddressView.tsx rename packages/uniswap/src/features/chains/{hooks.ts => hooks/useEnabledChains.ts} (61%) create mode 100644 packages/uniswap/src/features/chains/hooks/useFeatureFlaggedChainIds.ts create mode 100644 packages/uniswap/src/features/chains/hooks/useNewChainIds.ts create mode 100644 packages/uniswap/src/features/chains/hooks/useOrderedChainIds.ts create mode 100644 packages/uniswap/src/features/chains/hooks/useSupportedChainId.ts create mode 100644 packages/uniswap/src/features/ens/constants.ts create mode 100644 packages/uniswap/src/features/search/SearchResult.test.ts create mode 100644 packages/uniswap/src/features/tokens/TokenWarningFlagsTable.tsx create mode 100644 packages/uniswap/src/features/tokens/WarningInfoModalContainer.tsx rename packages/{wallet => uniswap}/src/features/transactions/refetchGQLQueriesSaga.ts (88%) create mode 100644 packages/uniswap/src/features/transactions/swap/form/footer/TradeWarningRow.tsx create mode 100644 packages/uniswap/src/features/transactions/swap/form/utils.test.ts create mode 100644 packages/uniswap/src/features/transactions/swap/form/utils.ts delete mode 100644 packages/uniswap/src/features/transactions/swap/hooks/useSwapWarningUtils.ts delete mode 100644 packages/uniswap/src/features/transactions/swap/modals/PriceImpactWarning.tsx create mode 100644 packages/uniswap/src/features/transactions/swap/settings/contexts/SwapSettingsContext.tsx rename packages/{wallet => uniswap}/src/features/unitags/constants.ts (84%) create mode 100644 packages/uniswap/src/i18n/locales/translations/fil-PH.json create mode 100644 packages/utilities/src/react/useBooleanState.test.ts create mode 100644 packages/utilities/src/react/useBooleanState.ts create mode 100644 packages/utilities/src/react/useHasValueChanged.test.ts create mode 100644 packages/utilities/src/react/useHasValueChanged.ts create mode 100644 packages/utilities/src/react/usePreviousWithLayoutEffect.test.ts create mode 100644 packages/utilities/src/react/usePreviousWithLayoutEffect.ts create mode 100644 packages/utilities/src/test/fixtures.ts create mode 100644 packages/wallet/src/components/settings/DappEllipsisDropdown/DappEllipsisDropdown.native.tsx create mode 100644 packages/wallet/src/components/settings/DappEllipsisDropdown/DappEllipsisDropdown.tsx rename apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/internal/EllipsisDropdown.tsx => packages/wallet/src/components/settings/DappEllipsisDropdown/DappEllipsisDropdown.web.tsx (60%) create mode 100644 packages/wallet/src/components/settings/DappEllipsisDropdown/internal/DappEllipsisDropdownIcon.tsx diff --git a/.depcheckrc b/.depcheckrc index 09ae52061bf..46f18f0596d 100644 --- a/.depcheckrc +++ b/.depcheckrc @@ -2,6 +2,7 @@ ignores: [ # Dependencies that depcheck thinks are unused but are actually used '@graphql-codegen/*', '@commitlint/*', + '@uniswap/eslint-config', 'i18next', 'moti', # Dependencies that depcheck thinks are missing but are actually present or never used diff --git a/.yarn/patches/@gorhom-bottom-sheet-npm-4.5.1-d8ef5d483d.patch b/.yarn/patches/@gorhom-bottom-sheet-npm-4.5.1-d8ef5d483d.patch new file mode 100644 index 00000000000..9e07e348352 --- /dev/null +++ b/.yarn/patches/@gorhom-bottom-sheet-npm-4.5.1-d8ef5d483d.patch @@ -0,0 +1,13 @@ +diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx +index 1050af591cedd3395c3f21553f9b125d85ca9d11..0761eb562be2af0ebccfda02f06a9ec79289d4ae 100644 +--- a/src/components/bottomSheet/BottomSheet.tsx ++++ b/src/components/bottomSheet/BottomSheet.tsx +@@ -501,7 +501,7 @@ const BottomSheetComponent = forwardRef( + animatedAnimationSource.value === ANIMATION_SOURCE.SNAP_POINT_CHANGE && + animatedAnimationState.value === ANIMATION_STATE.RUNNING + ) { +- return animatedNextPositionIndex.value; ++ return Math.max(animatedCurrentIndex.value, currentIndex); + } + + return currentIndex; diff --git a/.yarn/patches/@shopify-react-native-performance-npm-4.1.2-ec1fcc7507.patch b/.yarn/patches/@shopify-react-native-performance-npm-4.1.2-ec1fcc7507.patch new file mode 100644 index 00000000000..3fd95f85a42 --- /dev/null +++ b/.yarn/patches/@shopify-react-native-performance-npm-4.1.2-ec1fcc7507.patch @@ -0,0 +1,11 @@ +diff --git a/ios/ReactNativePerformance/ReactNativePerformance.m b/ios/ReactNativePerformance/ReactNativePerformance.m +index 485211356fc14de4205e6d0c7e06eb5116992e0b..dbb4bb5637656b7f62fc88249b4ed5402c22290d 100644 +--- a/ios/ReactNativePerformance/ReactNativePerformance.m ++++ b/ios/ReactNativePerformance/ReactNativePerformance.m +@@ -1,5 +1,5 @@ + #import "ReactNativePerformance.h" +-#import "ReactNativePerformance-Swift.h" ++#import + + static NSTimeInterval startupTimestamp = -1.0; + 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 6ecc8ad3f0d..6ae30f3e92a 100644 --- a/RELEASE +++ b/RELEASE @@ -1,4 +1,59 @@ -We’ve got some minor updates and improvements! - -- Balances update more quickly in your wallet after a transaction! -- Various bug fixes and performance improvements around sending, usernames, and more. +IPFS hash of the deployment: +- CIDv0: `QmREL7zj6bMggt5yrx5mfiWCnWzbP1xAoVTYAd2J3uFgtb` +- CIDv1: `bafybeibk6showu2rchiizehaujrhea6a3ebhhapm3ivjdrhnddgbx2u3tq` + +The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). + +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://bafybeibk6showu2rchiizehaujrhea6a3ebhhapm3ivjdrhnddgbx2u3tq.ipfs.dweb.link/ +- https://bafybeibk6showu2rchiizehaujrhea6a3ebhhapm3ivjdrhnddgbx2u3tq.ipfs.cf-ipfs.com/ +- [ipfs://QmREL7zj6bMggt5yrx5mfiWCnWzbP1xAoVTYAd2J3uFgtb/](ipfs://QmREL7zj6bMggt5yrx5mfiWCnWzbP1xAoVTYAd2J3uFgtb/) + +## 5.61.0 (2024-12-04) + + +### Features + +* **web:** add privacy choices nav and modal (#13703) 87a958d +* **web:** add the range chart into the creation flow (#14008) a0c8c02 +* **web:** reset confirmation modal for positions (#14080) 3485968 +* **web:** support v4 tick data in range input (#14040) 68211cc + + +### Bug Fixes + +* **web:** [1/n] remove react-spring usage (AnimatedDropdown) (#13891) 9af3573 +* **web:** [2/n] remove react-spring usage (modal) (#13904) b9a0890 +* **web:** android keyboard issue (#14266) 7b9834c +* **web:** bunch of v4 ui polish (#14117) d724fcb +* **web:** fix newly created v2 pairs (#14061) 5b30b6f +* **web:** fix refetching and caching issues due to the deadline (#14155) 46856f6 +* **web:** handle gql refetch balance for unknown tx types (#14060) 7413d91 +* **web:** hotfixing v4 fixes (#14244) f3c9411 +* **web:** increase size of pointer target for the filter dropdown (#14068) 2b36a2c +* **web:** op usdc sends (#14205) ecfe6df +* **web:** remove nondefault list tokens from common bases (#14203) 42d8203 +* **web:** remove top pools from mweb table (#14053) fa9080a +* **web:** sticky left column create pool (#14122) dcafb2e +* **web:** tiny v4 deposit polish (#14059) 002273a +* **web:** token protection polishes (#14038) fe36c5d +* **web:** update support articles for v4 and lp redesign (#14257) ec4508b +* **web:** v2 loading state (#14050) 79bed9c +* **web:** v4 LP flow cherrypicks (#14196) 0f2dc25 + + +### Continuous Integration + +* **web:** update sitemaps ef443f0 + + +### Tests + +* **web:** fix wallet connection test (#14047) 123862c + + diff --git a/VERSION b/VERSION index 46e05265dcc..bd5bf421e0e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -extension/1.9.0 \ No newline at end of file +web/5.61.0 \ No newline at end of file diff --git a/apps/extension/package.json b/apps/extension/package.json index c4d26e5b391..ed43f2c56a5 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -4,6 +4,7 @@ "browserslist": "last 2 chrome versions", "dependencies": { "@apollo/client": "3.10.4", + "@datadog/browser-logs": "5.20.0", "@datadog/browser-rum": "5.23.3", "@ethersproject/providers": "5.7.2", "@metamask/rpc-errors": "6.2.1", @@ -12,10 +13,10 @@ "@sentry/react": "7.80.0", "@sentry/webpack-plugin": "2.10.3", "@svgr/webpack": "8.0.1", - "@tamagui/core": "1.108.4", + "@tamagui/core": "1.114.4", "@types/uuid": "9.0.1", - "@uniswap/analytics-events": "2.38.0", - "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", + "@uniswap/analytics-events": "2.39.0", + "@uniswap/uniswapx-sdk": "2.1.0-beta.18", "@uniswap/universal-router-sdk": "4.5.2", "@uniswap/v3-sdk": "3.18.1", "@uniswap/v4-sdk": "1.10.3", @@ -31,7 +32,7 @@ "react-native-gesture-handler": "2.19.0", "react-native-reanimated": "3.15.0", "react-native-svg": "15.1.0", - "react-native-web": "0.19.10", + "react-native-web": "0.19.13", "react-qr-code": "2.0.12", "react-redux": "8.0.5", "react-router-dom": "6.10.0", @@ -51,37 +52,37 @@ "zod": "3.22.4" }, "devDependencies": { - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", - "@testing-library/dom": "^7.11.0", + "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", + "@testing-library/dom": "7.31.2", "@testing-library/react": "13.4.0", "@types/chrome": "0.0.254", "@types/jest": "29.5.0", - "@types/react": "^18.0.15", - "@types/react-dom": "^18.0.6", + "@types/react": "18.0.38", + "@types/react-dom": "18.2.15", "@types/redux-logger": "3.0.9", "@types/redux-persist-webextension-storage": "1.0.3", "@types/ua-parser-js": "0.7.31", "@uniswap/eslint-config": "workspace:^", "@welldone-software/why-did-you-render": "8.0.1", - "clean-webpack-plugin": "^4.0.0", - "concurrently": "^8.0.1", - "copy-webpack-plugin": "^11.0.0", - "esbuild-loader": "^3.0.1", + "clean-webpack-plugin": "4.0.0", + "concurrently": "8.2.2", + "copy-webpack-plugin": "11.0.0", + "esbuild-loader": "3.2.0", "eslint": "8.44.0", "jest": "29.7.0", "jest-chrome": "0.8.0", "jest-environment-jsdom": "29.5.0", "jest-extended": "4.0.1", - "mini-css-extract-plugin": "^2.7.6", - "react-refresh": "^0.14.0", - "serve": "^14.2.0", + "mini-css-extract-plugin": "2.9.1", + "react-refresh": "0.14.0", + "serve": "14.2.4", "statsig-js": "4.41.0", - "swc-loader": "^0.2.3", - "tamagui-loader": "1.108.4", + "swc-loader": "0.2.6", + "tamagui-loader": "1.114.4", "typescript": "5.3.3", "webpack": "5.90.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.13.1" + "webpack-cli": "5.1.4", + "webpack-dev-server": "4.15.1" }, "private": true, "scripts": { diff --git a/apps/extension/src/app/OnboardingApp.tsx b/apps/extension/src/app/OnboardingApp.tsx index 398de94fcef..3b46ad40ceb 100644 --- a/apps/extension/src/app/OnboardingApp.tsx +++ b/apps/extension/src/app/OnboardingApp.tsx @@ -39,6 +39,7 @@ import { initExtensionAnalytics } from 'src/app/utils/analytics' import { checksIfSupportsSidePanel } from 'src/app/utils/chrome' import { PrimaryAppInstanceDebuggerLazy } from 'src/store/PrimaryAppInstanceDebuggerLazy' import { getReduxPersistor, getReduxStore } from 'src/store/store' +import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import Trace from 'uniswap/src/features/telemetry/Trace' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' @@ -192,12 +193,14 @@ export default function OnboardingApp(): JSX.Element { - - - - - - + + + + + + + + diff --git a/apps/extension/src/app/PopupApp.tsx b/apps/extension/src/app/PopupApp.tsx index dcccdb561d1..b4ababc497e 100644 --- a/apps/extension/src/app/PopupApp.tsx +++ b/apps/extension/src/app/PopupApp.tsx @@ -17,6 +17,7 @@ import { getReduxPersistor, getReduxStore } from 'src/store/store' import { Button, Flex, Image, Text } from 'ui/src' import { CHROME_LOGO, UNISWAP_LOGO } from 'ui/src/assets' import { iconSizes, spacing } from 'ui/src/theme' +import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice' import Trace from 'uniswap/src/features/telemetry/Trace' @@ -132,14 +133,16 @@ export default function PopupApp(): JSX.Element { - - - - - - - - + + + + + + + + + + diff --git a/apps/extension/src/app/SidebarApp.tsx b/apps/extension/src/app/SidebarApp.tsx index 8540e616c78..c70497567b1 100644 --- a/apps/extension/src/app/SidebarApp.tsx +++ b/apps/extension/src/app/SidebarApp.tsx @@ -39,6 +39,7 @@ import { import { BackgroundToSidePanelRequestType } from 'src/background/messagePassing/types/requests' import { PrimaryAppInstanceDebuggerLazy } from 'src/store/PrimaryAppInstanceDebuggerLazy' import { getReduxPersistor, getReduxStore } from 'src/store/store' +import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice' import Trace from 'uniswap/src/features/telemetry/Trace' @@ -262,15 +263,17 @@ export default function SidebarApp(): JSX.Element { - - - - - - - - - + + + + + + + + + + + diff --git a/apps/extension/src/app/UnitagClaimApp.tsx b/apps/extension/src/app/UnitagClaimApp.tsx index d5a35563bfc..a3d35d65711 100644 --- a/apps/extension/src/app/UnitagClaimApp.tsx +++ b/apps/extension/src/app/UnitagClaimApp.tsx @@ -27,6 +27,7 @@ import { SentryAppNameTag, initializeSentry, sentryCreateHashRouter } from 'src/ import { initExtensionAnalytics } from 'src/app/utils/analytics' import { getReduxPersistor, getReduxStore } from 'src/store/store' import { Flex } from 'ui/src' +import { BlankUrlProvider } from 'uniswap/src/contexts/UrlContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import Trace from 'uniswap/src/features/telemetry/Trace' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' @@ -166,12 +167,14 @@ export default function UnitagClaimApp(): JSX.Element { - - - - - - + + + + + + + + diff --git a/apps/extension/src/app/components/Trace/TraceUserProperties.tsx b/apps/extension/src/app/components/Trace/TraceUserProperties.tsx index 6f22bc88469..005f75cb546 100644 --- a/apps/extension/src/app/components/Trace/TraceUserProperties.tsx +++ b/apps/extension/src/app/components/Trace/TraceUserProperties.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react' import { useColorScheme } from 'react-native' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' import { useCurrentLanguage } from 'uniswap/src/features/language/hooks' import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' diff --git a/apps/extension/src/app/datadog.ts b/apps/extension/src/app/datadog.ts index 235444a5f3f..fc6efd19243 100644 --- a/apps/extension/src/app/datadog.ts +++ b/apps/extension/src/app/datadog.ts @@ -1,3 +1,4 @@ +import { datadogLogs } from '@datadog/browser-logs' import { datadogRum } from '@datadog/browser-rum' import { getDatadogEnvironment } from 'src/app/version' import { config } from 'uniswap/src/config' @@ -15,12 +16,16 @@ export async function initializeDatadog(appName: string): Promise { return } - datadogRum.init({ - applicationId: config.datadogProjectId, + const sharedDatadogConfig = { clientToken: config.datadogClientToken, service: `extension-${getDatadogEnvironment()}`, env: getDatadogEnvironment(), version: process.env.VERSION, + } + + datadogRum.init({ + ...sharedDatadogConfig, + applicationId: config.datadogProjectId, sessionSampleRate: 100, sessionReplaySampleRate: 0, trackResources: true, @@ -44,6 +49,12 @@ export async function initializeDatadog(appName: string): Promise { }, }) + datadogLogs.init({ + ...sharedDatadogConfig, + site: 'datadoghq.com', + forwardErrorsToLogs: false, + }) + try { const userId = await getUniqueId() datadogRum.setUser({ diff --git a/apps/extension/src/app/features/accounts/CreateWalletModal.tsx b/apps/extension/src/app/features/accounts/CreateWalletModal.tsx index ce74e9ecf68..641abb6e7ec 100644 --- a/apps/extension/src/app/features/accounts/CreateWalletModal.tsx +++ b/apps/extension/src/app/features/accounts/CreateWalletModal.tsx @@ -6,7 +6,7 @@ import { iconSizes, opacify } from 'ui/src/theme' import { TextInput } from 'uniswap/src/components/input/TextInput' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalName } from 'uniswap/src/features/telemetry/constants' -import { shortenAddress } from 'uniswap/src/utils/addresses' +import { shortenAddress } from 'utilities/src/addresses' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' import { SignerMnemonicAccount } from 'wallet/src/features/wallet/accounts/types' diff --git a/apps/extension/src/app/features/accounts/EditLabelModal.tsx b/apps/extension/src/app/features/accounts/EditLabelModal.tsx index 04edfe13841..d08f6332a22 100644 --- a/apps/extension/src/app/features/accounts/EditLabelModal.tsx +++ b/apps/extension/src/app/features/accounts/EditLabelModal.tsx @@ -12,10 +12,10 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { OnboardingCardLoggingName } from 'uniswap/src/features/telemetry/types' +import { UNITAG_SUFFIX_NO_LEADING_DOT } from 'uniswap/src/features/unitags/constants' import { shortenAddress } from 'utilities/src/addresses' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' import { CardType, IntroCard, IntroCardGraphicType } from 'wallet/src/components/introCards/IntroCard' -import { UNITAG_SUFFIX_NO_LEADING_DOT } from 'wallet/src/features/unitags/constants' import { useCanActiveAddressClaimUnitag } from 'wallet/src/features/unitags/hooks' import { EditAccountAction, editAccountActions } from 'wallet/src/features/wallet/accounts/editAccountSaga' import { useDisplayName } from 'wallet/src/features/wallet/hooks' diff --git a/apps/extension/src/app/features/accounts/__snapshots__/AccountSwitcherScreen.test.tsx.snap b/apps/extension/src/app/features/accounts/__snapshots__/AccountSwitcherScreen.test.tsx.snap index c8ad496c2db..22a517dac34 100644 --- a/apps/extension/src/app/features/accounts/__snapshots__/AccountSwitcherScreen.test.tsx.snap +++ b/apps/extension/src/app/features/accounts/__snapshots__/AccountSwitcherScreen.test.tsx.snap @@ -16,43 +16,43 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` class=" t_light _dsp_contents is_Theme" >

@@ -78,13 +78,13 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
@@ -140,17 +140,17 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
- 0x​9eb6...a2ca + 0x​9EB67f...D9A2Ca Edit label @@ -187,7 +187,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` class="_dsp_contents" >
Add wallet @@ -248,43 +248,43 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` class=" t_light _dsp_contents is_Theme" >

@@ -310,13 +310,13 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
@@ -372,17 +372,17 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
- 0x​9eb6...a2ca + 0x​9EB67f...D9A2Ca Edit label @@ -419,7 +419,7 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` class="_dsp_contents" >
Add wallet diff --git a/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx b/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx index cb77db4f247..2e58cf05440 100644 --- a/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx +++ b/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx @@ -6,7 +6,7 @@ import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice' import { DappRequestType } from 'src/app/features/dappRequests/types/DappRequestTypes' import { Anchor, AnimatePresence, Button, Flex, Text, UniversalImage, UniversalImageResizeMode, styled } from 'ui/src' import { borderRadii, iconSizes } from 'ui/src/theme' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { UniverseChainId } from 'uniswap/src/features/chains/types' import { GasFeeResult } from 'uniswap/src/features/gas/types' import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils' diff --git a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx index 49f23b7ac43..e8c599d3393 100644 --- a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx +++ b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx @@ -5,7 +5,7 @@ import { ETH_ADDRESS } from 'src/app/features/dappRequests/requestContent/EthSen import { formatUnits, useSwapDetails } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/utils' import { SignTypedDataRequest, SwapSendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' import { DEFAULT_NATIVE_ADDRESS } from 'uniswap/src/features/chains/chainInfo' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { GasFeeResult } from 'uniswap/src/features/gas/types' diff --git a/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx b/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx index addd049e60f..5762d3a88ed 100644 --- a/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx +++ b/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx @@ -4,7 +4,7 @@ import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice' import { SendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' import { Flex, Text } from 'ui/src' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useGasFeeFormattedAmounts, useTransactionGasFee } from 'uniswap/src/features/gas/hooks' import { useActiveAccountAddressWithThrow, useDisplayName } from 'wallet/src/features/wallet/hooks' diff --git a/apps/extension/src/app/features/home/PortfolioActionButtons.tsx b/apps/extension/src/app/features/home/PortfolioActionButtons.tsx index a5eec5fe7dc..eb5591ad822 100644 --- a/apps/extension/src/app/features/home/PortfolioActionButtons.tsx +++ b/apps/extension/src/app/features/home/PortfolioActionButtons.tsx @@ -6,7 +6,7 @@ import { AppRoutes } from 'src/app/navigation/constants' import { navigate } from 'src/app/navigation/state' import { Flex, Text, getTokenValue, useMedia } from 'ui/src' import { ArrowDownCircle, Buy, CoinConvert, SendAction } from 'ui/src/components/icons' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' diff --git a/apps/extension/src/app/features/home/PortfolioHeader.tsx b/apps/extension/src/app/features/home/PortfolioHeader.tsx index 00aa8e8b346..ee0a4003928 100644 --- a/apps/extension/src/app/features/home/PortfolioHeader.tsx +++ b/apps/extension/src/app/features/home/PortfolioHeader.tsx @@ -22,8 +22,9 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { ExtensionScreens } from 'uniswap/src/types/screens/extension' -import { sanitizeAddressText, shortenAddress } from 'uniswap/src/utils/addresses' +import { sanitizeAddressText } from 'uniswap/src/utils/addresses' import { setClipboard } from 'uniswap/src/utils/clipboard' +import { shortenAddress } from 'utilities/src/addresses' import { extractNameFromUrl } from 'utilities/src/format/extractNameFromUrl' import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' diff --git a/apps/extension/src/app/features/home/SwitchNetworksModal.tsx b/apps/extension/src/app/features/home/SwitchNetworksModal.tsx index 22893cf2cd8..89c67d7ec21 100644 --- a/apps/extension/src/app/features/home/SwitchNetworksModal.tsx +++ b/apps/extension/src/app/features/home/SwitchNetworksModal.tsx @@ -9,7 +9,7 @@ import { Check, Power } from 'ui/src/components/icons' import { usePreventOverflowBelowFold } from 'ui/src/hooks/usePreventOverflowBelowFold' import { iconSizes } from 'ui/src/theme' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { UniverseChainId } from 'uniswap/src/features/chains/types' import { getChainLabel } from 'uniswap/src/features/chains/utils' import { pushNotification } from 'uniswap/src/features/notifications/slice' diff --git a/apps/extension/src/app/features/home/TokenBalanceList.tsx b/apps/extension/src/app/features/home/TokenBalanceList.tsx index f6925d0b4eb..a3e351c3b1b 100644 --- a/apps/extension/src/app/features/home/TokenBalanceList.tsx +++ b/apps/extension/src/app/features/home/TokenBalanceList.tsx @@ -10,7 +10,7 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal' import { uniswapUrls } from 'uniswap/src/constants/urls' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { ElementName, ModalName, SectionName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' diff --git a/apps/extension/src/app/features/onboarding/__snapshots__/KeyboardKey.test.tsx.snap b/apps/extension/src/app/features/onboarding/__snapshots__/KeyboardKey.test.tsx.snap index ecd2a180f1d..99fb978758d 100644 --- a/apps/extension/src/app/features/onboarding/__snapshots__/KeyboardKey.test.tsx.snap +++ b/apps/extension/src/app/features/onboarding/__snapshots__/KeyboardKey.test.tsx.snap @@ -13,10 +13,10 @@ exports[`KeyboardKey Component renders correctly with state Highlighted 1`] = ` class=" t_light _dsp_contents is_Theme" >
Shift @@ -41,10 +41,10 @@ exports[`KeyboardKey Component renders correctly with state KeyDown 1`] = ` class=" t_light _dsp_contents is_Theme" >
Shift @@ -69,10 +69,10 @@ exports[`KeyboardKey Component renders correctly with state KeyUp 1`] = ` class=" t_light _dsp_contents is_Theme" >
Shift diff --git a/apps/extension/src/app/features/onboarding/create/NameWallet.tsx b/apps/extension/src/app/features/onboarding/create/NameWallet.tsx index 0dae9d74e66..9f935269755 100644 --- a/apps/extension/src/app/features/onboarding/create/NameWallet.tsx +++ b/apps/extension/src/app/features/onboarding/create/NameWallet.tsx @@ -9,7 +9,7 @@ import { fonts, iconSizes } from 'ui/src/theme' import { UNISWAP_WEB_URL } from 'uniswap/src/constants/urls' import Trace from 'uniswap/src/features/telemetry/Trace' import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension' -import { shortenAddress } from 'uniswap/src/utils/addresses' +import { shortenAddress } from 'utilities/src/addresses' import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext' export function NameWallet(): JSX.Element { diff --git a/apps/extension/src/app/features/receive/__snapshots__/ReceiveScreen.test.tsx.snap b/apps/extension/src/app/features/receive/__snapshots__/ReceiveScreen.test.tsx.snap index 763be8cd161..359d1aaca3e 100644 --- a/apps/extension/src/app/features/receive/__snapshots__/ReceiveScreen.test.tsx.snap +++ b/apps/extension/src/app/features/receive/__snapshots__/ReceiveScreen.test.tsx.snap @@ -16,16 +16,16 @@ exports[`ReceiveScreen renders without error 1`] = ` class=" t_light _dsp_contents is_Theme" >
Receive @@ -52,7 +52,7 @@ exports[`ReceiveScreen renders without error 1`] = `
@@ -84,17 +84,17 @@ exports[`ReceiveScreen renders without error 1`] = `
- 0x​82D5...3Fa6 + 0x​82D56A...373Fa6
You can send and receive tokens and NFTs on all of our 12 supported networks. @@ -6011,11 +6011,11 @@ exports[`ReceiveScreen renders without error 1`] = ` >

@@ -6052,16 +6052,16 @@ exports[`ReceiveScreen renders without error 1`] = ` class=" t_light _dsp_contents is_Theme" >
Receive @@ -6088,7 +6088,7 @@ exports[`ReceiveScreen renders without error 1`] = `
@@ -6120,17 +6120,17 @@ exports[`ReceiveScreen renders without error 1`] = `
- 0x​82D5...3Fa6 + 0x​82D56A...373Fa6
You can send and receive tokens and NFTs on all of our 12 supported networks. @@ -12047,11 +12047,11 @@ exports[`ReceiveScreen renders without error 1`] = ` >

diff --git a/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/SettingsManageConnectionsScreen.tsx b/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/SettingsManageConnectionsScreen.tsx index 3b59c486275..e418c6323fa 100644 --- a/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/SettingsManageConnectionsScreen.tsx +++ b/apps/extension/src/app/features/settings/SettingsManageConnectionsScreen/SettingsManageConnectionsScreen.tsx @@ -2,14 +2,13 @@ import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' import { ScreenHeader } from 'src/app/components/layout/ScreenHeader' -import { removeDappConnection } from 'src/app/features/dapp/actions' +import { removeAllDappConnectionsForAccount, removeDappConnection } from 'src/app/features/dapp/actions' import { useAllDappConnectionsForActiveAccount } from 'src/app/features/dapp/hooks' import { dappStore } from 'src/app/features/dapp/store' -import { EllipsisDropdown } from 'src/app/features/settings/SettingsManageConnectionsScreen/internal/EllipsisDropdown' import { NoDappConnections } from 'src/app/features/settings/SettingsManageConnectionsScreen/internal/NoDappConnections' import { Flex, Text, TouchableArea, UniversalImage, useSporeColors } from 'ui/src' import { MinusCircle } from 'ui/src/components/icons' -import { borderRadii, breakpoints, iconSizes } from 'ui/src/theme' +import { borderRadii, breakpoints, fonts, gap, iconSizes } from 'ui/src/theme' import { pushNotification } from 'uniswap/src/features/notifications/slice' import { AppNotificationType } from 'uniswap/src/features/notifications/types' import Trace from 'uniswap/src/features/telemetry/Trace' @@ -19,6 +18,7 @@ import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { extractNameFromUrl } from 'utilities/src/format/extractNameFromUrl' import { extractUrlHost } from 'utilities/src/format/urls' import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder' +import { DappEllipsisDropdown } from 'wallet/src/components/settings/DappEllipsisDropdown/DappEllipsisDropdown' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' const MIN_SCREEN_WIDTH = breakpoints.xxs @@ -26,6 +26,11 @@ const HORIZONTAL_SPACING = 12 // when sidebar is at the minimum width (360px), this will allow 2 cards to cleanly fit per row const TILE_WIDTH = (MIN_SCREEN_WIDTH - 3 * HORIZONTAL_SPACING) / 2 +const titleVariant: keyof typeof fonts = 'body3' +const subtitleVariant: keyof typeof fonts = 'body4' +const textGap: number = gap.gap4 +const textAreaHeight = fonts[titleVariant].lineHeight + fonts[subtitleVariant].lineHeight + textGap + export function SettingsManageConnectionsScreen(): JSX.Element { const colors = useSporeColors() const dispatch = useDispatch() @@ -60,11 +65,67 @@ export function SettingsManageConnectionsScreen(): JSX.Element { dappUrls.map((dappUrl) => { const dappInfo = dappStore.getDappInfo(dappUrl) const name = extractNameFromUrl(dappUrl) + + const hostName = extractUrlHost(dappUrl) + const title = dappInfo?.displayName || hostName + + const DeleteDappButton = ( + + + + ) + + const DappIcon = ( + } + size={{ + height: iconSizes.icon32, + width: iconSizes.icon32, + }} + style={{ image: { borderRadius: borderRadii.rounded8 } }} + uri={dappInfo?.iconUrl} + /> + ) + + /** + * TEXT AREA; TITLE/SUBTITLE + * + * we only need to set the text area height because it is the only section with optional fields + */ + const Title = ( + + + {title} + + {hostName !== title && ( + + {hostName} + + )} + + ) + return ( - - - - - } - size={{ - height: iconSizes.icon32, - width: iconSizes.icon32, - }} - style={{ image: { borderRadius: borderRadii.rounded8 } }} - uri={dappInfo?.iconUrl} - /> - - {dappInfo?.displayName || name} - - {extractUrlHost(dappUrl)} - - + {DeleteDappButton} + {DappIcon} + {Title} ) }), @@ -112,7 +149,11 @@ export function SettingsManageConnectionsScreen(): JSX.Element { return ( : undefined} + rightColumn={ + hasConnections ? ( + + ) : undefined + } title={t('settings.setting.wallet.connections.title')} /> diff --git a/apps/extension/src/app/features/settings/SettingsScreen.tsx b/apps/extension/src/app/features/settings/SettingsScreen.tsx index 636628fab73..a4af469e930 100644 --- a/apps/extension/src/app/features/settings/SettingsScreen.tsx +++ b/apps/extension/src/app/features/settings/SettingsScreen.tsx @@ -32,11 +32,12 @@ import { RotatableChevron, Settings, ShieldQuestion, + Wrench, } from 'ui/src/components/icons' import { iconSizes } from 'ui/src/theme' import { uniswapUrls } from 'uniswap/src/constants/urls' import { resetUniswapBehaviorHistory } from 'uniswap/src/features/behaviorHistory/slice' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { FiatCurrency, ORDERED_CURRENCIES } from 'uniswap/src/features/fiatCurrency/constants' import { getFiatCurrencyName, useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' @@ -191,7 +192,7 @@ export function SettingsScreen(): JSX.Element { onPress={(): void => navigateTo(`${AppRoutes.Settings}/${SettingsRoutes.Privacy}`)} /> locationState?.initialTransactionState ?? initialState) + + const swapPrefilledState = useSwapPrefilledState(initialTransactionState) return ( diff --git a/apps/extension/src/app/features/unitags/UnitagConfirmationScreen.tsx b/apps/extension/src/app/features/unitags/UnitagConfirmationScreen.tsx index 72b44eaf3ef..560efe5fcf8 100644 --- a/apps/extension/src/app/features/unitags/UnitagConfirmationScreen.tsx +++ b/apps/extension/src/app/features/unitags/UnitagConfirmationScreen.tsx @@ -5,9 +5,9 @@ import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsC import { useUnitagClaimContext } from 'src/app/features/unitags/UnitagClaimContext' import { closeCurrentTab } from 'src/app/navigation/utils' import { Button, Flex, Text } from 'ui/src' +import { UNITAG_SUFFIX } from 'uniswap/src/features/unitags/constants' import { logger } from 'utilities/src/logger/logger' import { UnitagWithProfilePicture } from 'wallet/src/features/unitags/UnitagWithProfilePicture' -import { UNITAG_SUFFIX } from 'wallet/src/features/unitags/constants' import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks' export function UnitagConfirmationScreen(): JSX.Element { diff --git a/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx b/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx index 98d796211d9..92e516e9902 100644 --- a/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx +++ b/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx @@ -5,7 +5,7 @@ import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard' import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants' import { navigate } from 'src/app/navigation/state' import { SidebarLocationState, focusOrCreateTokensExploreTab } from 'src/app/navigation/utils' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { CopyNotificationType } from 'uniswap/src/features/notifications/types' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' diff --git a/apps/extension/src/manifest.json b/apps/extension/src/manifest.json index 709f87f3211..e4bbd1b8be4 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.9.0", + "version": "1.11.0", "minimum_chrome_version": "116", "icons": { "16": "assets/icon16.png", diff --git a/apps/extension/tsconfig.json b/apps/extension/tsconfig.json index 4ca06406cec..423bf9df183 100644 --- a/apps/extension/tsconfig.json +++ b/apps/extension/tsconfig.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/tsconfig", "display": "Web App", - "extends": "tsconfig/nextjs.json", + "extends": "../../config/tsconfig/nextjs.json", "include": [ "**/*.ts", "**/*.tsx", diff --git a/apps/mobile/README.md b/apps/mobile/README.md index d4b08b71d76..215ce86968d 100644 --- a/apps/mobile/README.md +++ b/apps/mobile/README.md @@ -218,6 +218,20 @@ You can also run the app from Xcode, which is necessary for any Swift related ch Hopefully you now (after a few minutes) see the Uniswap Wallet running in the iOS Simulator! +### Using Radon IDE (VSCode/Cursor Extension) + +[Radon IDE](https://marketplace.visualstudio.com/items?itemName=swmansion.react-native-ide&ssr=false#review-details) is a relatively new VSCode extension build by Software Mansion. TLDR; its tagline is + +> A better developer experience for React Native developers + +It's not perfect, but it's great to have in the toolbox. One noteworthy feature is the ability to click on any piece of UI and be able to inspect the component hierarchy + jump straight into the relevant code. There's also support for breakpoints in VSCode/Cursor, better logging, instant replay of your session, and the ability to adjust common device settings on the fly. + +To get started, you should already be able to build the iOS app (either in XCode or via the cli). Install the extension, open it, and follow the onboarding instructions. + +One you have a device configured, it will start to build. If/when successful, you'll see the device simulator/emulator in the sidebar. + +In `.vscode/launch.json`, you will see configurations for each platform. This is where you can specify the fingerprint command. The fingerprint is a hash of the build environment, and Radon uses it to determine if the build has changed so that it knows when to re-run the build process (i.e. only on native code changes). There are more complex implementations of this, but this is a simple first step. + #### Running on a Physical iOS Device 1. Follow all steps listed above. diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index 03358f7c722..d19d4c6b01c 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -89,9 +89,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) { apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle" } -def devVersionName = "1.39" -def betaVersionName = "1.39" -def prodVersionName = "1.39" +def devVersionName = "1.41" +def betaVersionName = "1.41" +def prodVersionName = "1.41" android { ndkVersion rootProject.ext.ndkVersion diff --git a/apps/mobile/android/app/src/main/ic_launcher-playstore.png b/apps/mobile/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..f48a409748e0e37eae6ccf1ec4098352ef34461f GIT binary patch literal 77585 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 { override fun receiveCommand(root: ComposeView, commandId: String?, args: ReadableArray?) { super.receiveCommand(root, commandId, args) when (commandId) { - COMMAND_HANDLE_SUBMIT -> viewModel.handleSubmit() + COMMAND_HANDLE_SUBMIT -> { + viewModel.handleSubmit() + } + COMMAND_FOCUS -> { + viewModel.focus() + } + COMMAND_BLUR -> { + viewModel.blur() + } else -> Unit } } @@ -150,6 +158,8 @@ class SeedPhraseInputViewManager : ViewGroupManager() { private const val EVENT_MNEMONIC_STORED = "onMnemonicStored" private const val EVENT_HEIGHT_MEASURED = "onHeightMeasured" private const val COMMAND_HANDLE_SUBMIT = "handleSubmit" + private const val COMMAND_FOCUS = "focus" + private const val COMMAND_BLUR = "blur" private const val FIELD_MNEMONIC_ID = "mnemonicId" private const val FIELD_CAN_SUBMIT = "canSubmit" private const val FIELD_HEIGHT = "height" diff --git a/apps/mobile/android/app/src/main/java/com/uniswap/onboarding/import/SeedPhraseInputViewModel.kt b/apps/mobile/android/app/src/main/java/com/uniswap/onboarding/import/SeedPhraseInputViewModel.kt index f9d839e1dda..f4b368c9ef3 100644 --- a/apps/mobile/android/app/src/main/java/com/uniswap/onboarding/import/SeedPhraseInputViewModel.kt +++ b/apps/mobile/android/app/src/main/java/com/uniswap/onboarding/import/SeedPhraseInputViewModel.kt @@ -13,6 +13,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import android.content.Context +import android.view.inputmethod.InputMethodManager +import android.view.View class SeedPhraseInputViewModel( private val ethersRs: RnEthersRs, @@ -63,6 +66,17 @@ class SeedPhraseInputViewModel( private var validateLastWordTimeout: Long = 1000 private var validateLastWordJob: Job? = null + var isFocused by mutableStateOf(false) + private set + + fun focus() { + isFocused = true + } + + fun blur() { + isFocused = false + } + fun handleInputChange(value: TextFieldValue) { input = value @@ -154,4 +168,5 @@ class SeedPhraseInputViewModel( private const val MIN_LENGTH = 12 private const val MAX_LENGTH = 24 } + } diff --git a/apps/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png b/apps/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png deleted file mode 100644 index 7b89676a489fb5d23191e61f39691ad6e992d8b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24608 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 b&1(N2~$8x diff --git a/apps/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png deleted file mode 100644 index a873e0e0f9763d56f89600d9352c9f754b935a0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49625 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�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

+
-
-
-
- + + +
`; exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` -.c8 { +.c6 { box-sizing: border-box; margin: 0; min-width: 0; } -.c9 { +.c7 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -742,7 +688,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` gap: 4px; } -.c19 { +.c17 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -759,7 +705,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` justify-content: flex-start; } -.c24 { +.c22 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -777,14 +723,14 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` gap: 12px; } -.c21 { +.c19 { -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; } -.c14 { +.c12 { color: #222222; -webkit-letter-spacing: -0.01em; -moz-letter-spacing: -0.01em; @@ -792,7 +738,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` letter-spacing: -0.01em; } -.c16 { +.c14 { color: #7D7D7D; -webkit-letter-spacing: -0.01em; -moz-letter-spacing: -0.01em; @@ -800,7 +746,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` letter-spacing: -0.01em; } -.c10 { +.c8 { color: #222222; cursor: pointer; -webkit-text-decoration: none; @@ -810,15 +756,15 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` transition-duration: 125ms; } -.c10:hover { +.c8:hover { opacity: 0.6; } -.c10:active { +.c8:active { opacity: 0.4; } -.c6 { +.c4 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -829,15 +775,15 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` font-weight: 500; } -.c6:hover { +.c4:hover { opacity: 0.6; } -.c6:active { +.c4:active { opacity: 0.4; } -.c28 { +.c26 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -851,7 +797,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` width: 100%; } -.c25 { +.c23 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -886,30 +832,30 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` user-select: none; } -.c25:active .c27 { +.c23:active .c25 { background-color: #B8C0DC3d; } -.c25:focus .c27 { +.c23:focus .c25 { background-color: #B8C0DC3d; } -.c25:hover .c27 { +.c23:hover .c25 { background-color: #98A1C014; } -.c25:disabled { +.c23:disabled { cursor: default; opacity: 0.6; } -.c25:disabled:active .c27, -.c25:disabled:focus .c27, -.c25:disabled:hover .c27 { +.c23:disabled:active .c25, +.c23:disabled:focus .c25, +.c23:disabled:hover .c25 { background-color: transparent; } -.c29 { +.c27 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -944,73 +890,30 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` user-select: none; } -.c29:active .c27 { +.c27:active .c25 { background-color: #B8C0DC3d; } -.c29:focus .c27 { +.c27:focus .c25 { background-color: #B8C0DC3d; } -.c29:hover .c27 { +.c27:hover .c25 { background-color: #98A1C014; } -.c29:disabled { +.c27:disabled { cursor: default; opacity: 0.6; } -.c29:disabled:active .c27, -.c29:disabled:focus .c27, -.c29:disabled:hover .c27 { +.c27:disabled:active .c25, +.c27:disabled:focus .c25, +.c27:disabled:hover .c25 { background-color: transparent; } -.c0 { - will-change: transform,opacity; -} - -.c0[data-reach-dialog-overlay] { - z-index: 1040; - background-color: transparent; - overflow: hidden; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - overflow-y: scroll; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - background-color: rgba(0,0,0,0.60); -} - -.c1 { - overflow-y: auto; -} - -.c1[data-reach-dialog-content] { - margin: auto; - background-color: #F9F9F9; - border: 1px solid #22222212; - box-shadow: 8px 12px 20px rgba(51,53,72,0.04),4px 6px 12px rgba(51,53,72,0.02),4px 4px 8px rgba(51,53,72,0.04); - padding: 0px; - width: 50vw; - overflow-y: auto; - overflow-x: hidden; - max-height: 90vh; - max-width: 420px; - display: inline-table; - border-radius: 20px; -} - -.c7 { +.c5 { width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; @@ -1024,17 +927,17 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` stroke: none; } -.c7:hover { +.c5:hover { background: #22222212; color: #222222; opacity: unset; } -.c7:hover path { +.c5:hover path { fill: #222222; } -.c2 { +.c0 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1049,7 +952,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` gap: 24px; } -.c11 { +.c9 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1064,7 +967,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` gap: 16px; } -.c13 { +.c11 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1079,7 +982,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` gap: 8px; } -.c18 { +.c16 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1093,7 +996,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` justify-content: flex-start; } -.c3 { +.c1 { width: 100%; -webkit-align-items: center; -webkit-box-align: center; @@ -1101,15 +1004,14 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` align-items: center; } -.c4 { +.c2 { background-color: #FFFFFF; - outline: 1px solid #22222212; - border-radius: 20px; + border-radius: 16px; padding: 16px 24px 24px 24px; width: 100%; } -.c12 { +.c10 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1128,14 +1030,14 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` border-radius: 12px; } -.c15 { +.c13 { font-size: 24px; line-height: 32px; text-align: center; font-weight: 500; } -.c17 { +.c15 { font-size: 16px; font-weight: 500; line-height: 24px; @@ -1146,7 +1048,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` text-align: center; } -.c26 { +.c24 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1160,7 +1062,7 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` border-radius: 12px; } -.c30 { +.c28 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1174,56 +1076,32 @@ exports[`CancelOrdersDialog should render order cancel correctly 1`] = ` border-radius: 12px; } -.c5 { +.c3 { padding: 4px 0px; } -.c22 { +.c20 { cursor: auto; color: #7D7D7D; } -.c23 { +.c21 { text-align: right; overflow-wrap: break-word; } -.c20 { +.c18 { border-top: 1px solid #22222212; margin-top: 16px; padding-top: 16px; } -@media screen and (max-width:640px) { - .c0[data-reach-dialog-overlay] { - -webkit-align-items: flex-end; - -webkit-box-align: flex-end; - -ms-flex-align: flex-end; - align-items: flex-end; - } -} - -@media screen and (max-width:768px) { - .c1[data-reach-dialog-content] { - width: 65vw; - } -} - -@media screen and (max-width:640px) { - .c1[data-reach-dialog-content] { - margin: 0; - width: 100vw; - border-radius: 20px; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } -} -
1 open limit diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx index 600252fb301..999e0fe18c4 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx @@ -13,7 +13,7 @@ import { useNavigate } from 'react-router-dom' import { ThemedText } from 'theme/components' import { capitalize } from 'tsafe' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { GqlChainId } from 'uniswap/src/features/chains/types' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { t } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx index 62d21d21f23..4489202a91e 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx @@ -13,7 +13,7 @@ import InfiniteScroll from 'react-infinite-scroll-component' import { useNavigate } from 'react-router-dom' import { Gallery } from 'ui/src/components/icons/Gallery' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { t } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/PoolsTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/PoolsTab.tsx index ff549dfce27..9273bec9bb3 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/PoolsTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/PoolsTab.tsx @@ -1,134 +1,102 @@ import { InterfaceElementName } from '@uniswap/analytics-events' -import { Position } from '@uniswap/v3-sdk' +// eslint-disable-next-line no-restricted-imports +import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { ExpandoRow } from 'components/AccountDrawer/MiniPortfolio/ExpandoRow' -import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache' -import { useFeeValues } from 'components/AccountDrawer/MiniPortfolio/Pools/hooks' -import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions' -import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' -import PortfolioRow, { - PortfolioSkeleton, - PortfolioTabWrapper, -} from 'components/AccountDrawer/MiniPortfolio/PortfolioRow' +import { PortfolioSkeleton, PortfolioTabWrapper } from 'components/AccountDrawer/MiniPortfolio/PortfolioRow' import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' -import { MouseoverTooltip } from 'components/Tooltip' -import Row from 'components/deprecated/Row' +import { LiquidityPositionCard } from 'components/Liquidity/LiquidityPositionCard' +import { PositionInfo } from 'components/Liquidity/types' +import { getPositionUrl, parseRestPosition } from 'components/Liquidity/utils' +import { ZERO_ADDRESS } from 'constants/misc' import { useAccount } from 'hooks/useAccount' -import { useFilterPossiblyMaliciousPositions } from 'hooks/useFilterPossiblyMaliciousPositions' import { useSwitchChain } from 'hooks/useSwitchChain' -import styled from 'lib/styled-components' import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent' import { useCallback, useMemo, useReducer } from 'react' import { useNavigate } from 'react-router-dom' -import { ThemedText } from 'theme/components' -import { BIPS_BASE } from 'uniswap/src/constants/misc' +import { TouchableArea } from 'ui/src' +import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' +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 { t } from 'uniswap/src/i18n' -import { NumberType, useFormatter } from 'utils/formatNumbers' - -/** - * Takes an array of PositionInfo objects (format used by the Uniswap Labs gql API). - * The hook access PositionInfo.details (format used by the NFT position contract), - * filters the PositionDetails data for malicious content, - * and then returns the original data in its original format. - */ -export function useFilterPossiblyMaliciousPositionInfo(positions: PositionInfo[] | undefined): PositionInfo[] { - const tokenIdsToPositionInfo: Record = useMemo( - () => - positions - ? positions.reduce((acc, position) => ({ ...acc, [position.details.tokenId.toString()]: position }), {}) - : {}, - [positions], - ) - const positionDetails = useMemo(() => positions?.map((position) => position.details) ?? [], [positions]) - const filteredPositionDetails = useFilterPossiblyMaliciousPositions(positionDetails) - return useMemo( - () => filteredPositionDetails.map((positionDetails) => tokenIdsToPositionInfo[positionDetails.tokenId.toString()]), - [filteredPositionDetails, tokenIdsToPositionInfo], - ) +function isPositionInfo(position: PositionInfo | undefined): position is PositionInfo { + return !!position +} + +function getPositionKey(position: PositionInfo) { + const { chainId } = position + if (position.version === ProtocolVersion.V2) { + return `${position.liquidityToken.address}-${chainId}` + } + + return `${position.tokenId}-${chainId}` } export default function Pools({ account }: { account: string }) { - const { positions, loading } = useMultiChainPositions(account) - const filteredPositions = useFilterPossiblyMaliciousPositionInfo(positions) - const [showClosed, toggleShowClosed] = useReducer((showClosed) => !showClosed, false) + const isLPRedesignEnabled = useFeatureFlag(FeatureFlags.LPRedesign) + + const { data, isLoading } = useGetPositionsQuery({ + address: account, + positionStatuses: [PositionStatus.IN_RANGE, PositionStatus.OUT_OF_RANGE], + protocolVersions: isLPRedesignEnabled + ? [ProtocolVersion.V2, ProtocolVersion.V3, ProtocolVersion.V4] + : [ProtocolVersion.V2, ProtocolVersion.V3], + }) + + const { data: closedData } = useGetPositionsQuery({ + address: account, + positionStatuses: [PositionStatus.CLOSED], + protocolVersions: isLPRedesignEnabled + ? [ProtocolVersion.V2, ProtocolVersion.V3, ProtocolVersion.V4] + : [ProtocolVersion.V2, ProtocolVersion.V3], + }) + + const openPositions = useMemo(() => data?.positions.map(parseRestPosition).filter(isPositionInfo), [data?.positions]) + const closedPositions = useMemo( + () => closedData?.positions.map(parseRestPosition).filter(isPositionInfo), + [closedData?.positions], + ) - const [openPositions, closedPositions] = useMemo(() => { - const openPositions: PositionInfo[] = [] - const closedPositions: PositionInfo[] = [] - for (let i = 0; i < filteredPositions.length; i++) { - const position = filteredPositions[i] - if (position.closed) { - closedPositions.push(position) - } else { - openPositions.push(position) - } - } - return [openPositions, closedPositions] - }, [filteredPositions]) + const [showClosed, toggleShowClosed] = useReducer((showClosed) => !showClosed, false) const accountDrawer = useAccountDrawer() - if (!filteredPositions || loading) { + if (!openPositions && isLoading) { return } - if (filteredPositions.length === 0) { + if (!openPositions || (openPositions?.length === 0 && closedPositions?.length === 0)) { return } return ( {openPositions.map((positionInfo) => ( - + ))} - - {closedPositions.map((positionInfo) => ( - - ))} - + {closedPositions && closedPositions.length > 0 && ( + + {closedPositions.map((positionInfo) => ( + + ))} + + )} ) } -const ActiveDot = styled.span<{ closed: boolean; outOfRange: boolean }>` - background-color: ${({ theme, closed, outOfRange }) => - closed ? theme.neutral2 : outOfRange ? theme.deprecated_accentWarning : theme.success}; - border-radius: 50%; - height: 8px; - width: 8px; - margin-left: 4px; - margin-top: 1px; -` - -function calculateLiquidityValue(price0: number | undefined, price1: number | undefined, position: Position) { - if (!price0 || !price1) { - return undefined - } - - const value0 = parseFloat(position.amount0.toExact()) * price0 - const value1 = parseFloat(position.amount1.toExact()) * price1 - return value0 + value1 -} - function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) { - const { formatNumber } = useFormatter() - - const { chainId, position, pool, details, inRange, closed } = positionInfo + const isLPRedesignEnabled = useFeatureFlag(FeatureFlags.LPRedesign) - const { priceA, priceB, fees: feeValue } = useFeeValues(positionInfo) - const liquidityValue = calculateLiquidityValue(priceA, priceB, position) + const { tokenId, chainId, currency0Amount, currency1Amount } = positionInfo + const token0 = currency0Amount.currency + const token1 = currency1Amount.currency const navigate = useNavigate() const accountDrawer = useAccountDrawer() @@ -138,66 +106,32 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) { if (account.chainId !== chainId) { await switchChain(chainId) } + accountDrawer.close() - navigate('/pool/' + details.tokenId) - }, [account.chainId, chainId, switchChain, accountDrawer, navigate, details.tokenId]) + + const positionUrl = isLPRedesignEnabled + ? getPositionUrl(positionInfo) + : positionInfo.version === ProtocolVersion.V3 + ? '/pool/' + tokenId + : '/pools/v2' + navigate(positionUrl) + }, [account.chainId, chainId, switchChain, accountDrawer, navigate, tokenId, isLPRedesignEnabled, positionInfo]) const analyticsEventProperties = useMemo( () => ({ chain_id: chainId, - pool_token_0_symbol: pool.token0.symbol, - pool_token_1_symbol: pool.token1.symbol, - pool_token_0_address: pool.token0.address, - pool_token_1_address: pool.token1.address, + pool_token_0_symbol: token0.symbol, + pool_token_1_symbol: token1.symbol, + pool_token_0_address: token0.isToken ? token0.wrapped.address : ZERO_ADDRESS, + pool_token_1_address: token1.isToken ? token1.wrapped.address : ZERO_ADDRESS, }), - [chainId, pool.token0.address, pool.token0.symbol, pool.token1.address, pool.token1.symbol], + [chainId, token0, token1], ) return ( - } - title={ - - - {pool.token0.symbol} / {pool.token1?.symbol} - - - } - descriptor={{`${pool.fee / BIPS_BASE}%`}} - right={ - <> - - {`${formatNumber({ - input: liquidityValue, - type: NumberType.PortfolioBalance, - })} (liquidity) + ${formatNumber({ - input: feeValue, - type: NumberType.PortfolioBalance, - })} (fees)`} -
- } - > - - {formatNumber({ - input: (liquidityValue ?? 0) + (feeValue ?? 0), - type: NumberType.PortfolioBalance, - })} - - - - - - {closed ? t('common.closed') : inRange ? t('common.withinRange') : t('common.outOfRange')} - - - - - } - /> + + + ) } diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts index 05e9598f915..344c8440260 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts @@ -1,8 +1,4 @@ -import { - MULTICALL_ADDRESSES, - Token, - NONFUNGIBLE_POSITION_MANAGER_ADDRESSES as V3NFT_ADDRESSES, -} from '@uniswap/sdk-core' +import { MULTICALL_ADDRESSES, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES as V3NFT_ADDRESSES } from '@uniswap/sdk-core' import type { AddressMap } from '@uniswap/smart-order-router' import NFTPositionManagerJSON from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json' import MulticallJSON from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json' @@ -12,14 +8,14 @@ import { RPC_PROVIDERS } from 'constants/providers' import { BaseContract } from 'ethers/lib/ethers' import { toContractInput } from 'graphql/data/util' import { useAccount } from 'hooks/useAccount' -import useStablecoinPrice from 'hooks/useStablecoinPrice' import { useMemo } from 'react' import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'uniswap/src/abis/types/v3' import { ContractInput, useUniswapPricesQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { useEnabledChains, useIsSupportedChainIdCallback } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' +import { useIsSupportedChainIdCallback } from 'uniswap/src/features/chains/hooks/useSupportedChainId' import { UniverseChainId } from 'uniswap/src/features/chains/types' import { getContract } from 'utilities/src/contracts/getContract' import { CurrencyKey, currencyKey, currencyKeyFromGraphQL } from 'utils/currencyKey' @@ -27,7 +23,7 @@ import { CurrencyKey, currencyKey, currencyKeyFromGraphQL } from 'utils/currency type ContractMap = { [key: number]: T } // Constructs a chain-to-contract map, using the wallet's provider when available -export function useContractMultichain( +function useContractMultichain( addressMap: AddressMap, ABI: any, chainIds?: UniverseChainId[], @@ -97,21 +93,3 @@ export function usePoolPriceMap(positions: PositionInfo[] | undefined) { return { priceMap, pricesLoading: loading && !data } } - -function useFeeValue(token: Token, fee: number | undefined, queriedPrice: number | undefined) { - const { price: stablecoinPrice } = useStablecoinPrice(!queriedPrice ? token : undefined) - return useMemo(() => { - // Prefers gql price, as fetching stablecoinPrice will trigger multiple infura calls for each pool position - const price = queriedPrice ?? (stablecoinPrice ? parseFloat(stablecoinPrice.toSignificant()) : undefined) - const feeValue = fee && price ? fee * price : undefined - - return [price, feeValue] - }, [fee, queriedPrice, stablecoinPrice]) -} - -export function useFeeValues(position: PositionInfo) { - const [priceA, feeValueA] = useFeeValue(position.pool.token0, position.fees?.[0], position.prices?.[0]) - const [priceB, feeValueB] = useFeeValue(position.pool.token1, position.fees?.[1], position.prices?.[1]) - - return { priceA, priceB, fees: (feeValueA || 0) + (feeValueB || 0) } -} diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx index b585c1566f0..f95e963a42e 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx @@ -20,7 +20,7 @@ import { PositionDetails } from 'types/position' import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'uniswap/src/abis/types/v3' import { UniswapV3PoolInterface } from 'uniswap/src/abis/types/v3/UniswapV3Pool' import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { UniverseChainId } from 'uniswap/src/features/chains/types' import { logger } from 'utilities/src/logger/logger' import { DEFAULT_ERC20_DECIMALS } from 'utilities/src/tokens/constants' diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx index ece508a6229..a8b0ea01091 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx @@ -17,7 +17,7 @@ import { useCallback, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { EllipsisStyle, ThemedText } from 'theme/components' import { Text, Tooltip } from 'ui/src' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { useTranslation } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx index 51a451309e4..b471d242a9b 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx @@ -127,7 +127,7 @@ const TransactionTitleTable: { [key in TransactionType]: { [state in Transaction [TransactionStatus.Confirmed]: t('common.added.v2.liquidity'), [TransactionStatus.Failed]: t('common.add.v2.liquidity.failed'), }, - [TransactionType.MIGRATE_LIQUIDITY_V3]: { + [TransactionType.MIGRATE_LIQUIDITY_V2_TO_V3]: { [TransactionStatus.Pending]: t('common.migrating.liquidity'), [TransactionStatus.Confirmed]: t('common.migrated.liquidity'), [TransactionStatus.Failed]: t('common.migrate.liquidity.failed'), @@ -152,6 +152,16 @@ const TransactionTitleTable: { [key in TransactionType]: { [state in Transaction [TransactionStatus.Confirmed]: t('common.liquidity.removed'), [TransactionStatus.Failed]: t('common.remove.liquidity.failed'), }, + [TransactionType.CREATE_POSITION]: { + [TransactionStatus.Pending]: t('position.create.modal.header'), + [TransactionStatus.Confirmed]: t('pool.createdPosition'), + [TransactionStatus.Failed]: t('pool.createdPosition.failed'), + }, + [TransactionType.MIGRATE_LIQUIDITY_V3_TO_V4]: { + [TransactionStatus.Pending]: t('common.migrating.liquidity'), + [TransactionStatus.Confirmed]: t('common.migrated.liquidity'), + [TransactionStatus.Failed]: t('common.migrate.liquidity.failed'), + }, [TransactionType.BRIDGE]: { [TransactionStatus.Pending]: t('common.swapping'), [TransactionStatus.Confirmed]: t('common.swapped'), @@ -184,11 +194,13 @@ export const CancelledTransactionTitleTable: { [key in TransactionType]: string [TransactionType.DEPOSIT_LIQUIDITY_STAKING]: t('common.deposit.cancelled'), [TransactionType.WITHDRAW_LIQUIDITY_STAKING]: t('common.withdrawal.cancelled'), [TransactionType.ADD_LIQUIDITY_V2_POOL]: t('common.add.v2.liquidity.cancelled'), - [TransactionType.MIGRATE_LIQUIDITY_V3]: t('common.migrate.liquidity.cancelled'), + [TransactionType.MIGRATE_LIQUIDITY_V2_TO_V3]: t('common.migrate.liquidity.cancelled'), [TransactionType.SUBMIT_PROPOSAL]: t('common.submit.proposal.cancelled'), [TransactionType.LIMIT]: t('common.limit.cancelled'), [TransactionType.INCREASE_LIQUIDITY]: t('common.add.liquidity.cancelled'), [TransactionType.DECREASE_LIQUIDITY]: t('common.remove.liquidity.cancelled'), + [TransactionType.CREATE_POSITION]: t('pool.createdPosition.cancelled'), + [TransactionType.MIGRATE_LIQUIDITY_V3_TO_V4]: t('common.migrate.liquidity.cancelled'), [TransactionType.BRIDGE]: t('common.swap.cancelled'), } diff --git a/apps/web/src/components/AccountDrawer/SettingsMenu.tsx b/apps/web/src/components/AccountDrawer/SettingsMenu.tsx index f5344aeb7fe..75f96b9323d 100644 --- a/apps/web/src/components/AccountDrawer/SettingsMenu.tsx +++ b/apps/web/src/components/AccountDrawer/SettingsMenu.tsx @@ -6,14 +6,13 @@ import { SpamToggle } from 'components/AccountDrawer/SpamToggle' import { TestnetsToggle } from 'components/AccountDrawer/TestnetsToggle' import Column from 'components/deprecated/Column' import Row from 'components/deprecated/Row' -import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency' -import { useActiveLanguage } from 'hooks/useActiveLocale' import styled from 'lib/styled-components' import { ReactNode } from 'react' import { ChevronRight } from 'react-feather' import { ClickableStyle, ThemedText } from 'theme/components' import ThemeToggle from 'theme/components/ThemeToggle' -import { useLanguageInfo } from 'uniswap/src/features/language/hooks' +import { useAppFiatCurrency } from 'uniswap/src/features/fiatCurrency/hooks' +import { useCurrentLanguage, useLanguageInfo } from 'uniswap/src/features/language/hooks' import { Trans } from 'uniswap/src/i18n' const Container = styled(Column)` @@ -70,8 +69,8 @@ export default function SettingsMenu({ openLanguageSettings: () => void openLocalCurrencySettings: () => void }) { - const activeLanguage = useActiveLanguage() - const activeLocalCurrency = useActiveLocalCurrency() + const activeLanguage = useCurrentLanguage() + const activeLocalCurrency = useAppFiatCurrency() const languageInfo = useLanguageInfo(activeLanguage) return ( diff --git a/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx b/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx index 96150ddfde7..a6b6bfc4b2c 100644 --- a/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx +++ b/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx @@ -1,6 +1,6 @@ import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle' import { useDispatch } from 'react-redux' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useHideSmallBalancesSetting } from 'uniswap/src/features/settings/hooks' import { setHideSmallBalances } from 'uniswap/src/features/settings/slice' import { t } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx b/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx index a12a405cfff..c27e27a2b11 100644 --- a/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx +++ b/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx @@ -1,7 +1,7 @@ import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle' import { useDispatch } from 'react-redux' import { useOpenModal } from 'state/application/hooks' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { t } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/AccountDrawer/UniwalletModal.tsx b/apps/web/src/components/AccountDrawer/UniwalletModal.tsx index eb6e12c6b01..f6193ee4b8c 100644 --- a/apps/web/src/components/AccountDrawer/UniwalletModal.tsx +++ b/apps/web/src/components/AccountDrawer/UniwalletModal.tsx @@ -1,12 +1,9 @@ import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events' import MobileAppLogo from 'assets/svg/uniswap_app_logo.svg' -import Modal from 'components/Modal' -import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' import { useConnect } from 'hooks/useConnect' import { useCallback, useEffect, useState } from 'react' import { CloseIcon } from 'theme/components' -import { Button, Flex, Image, QRCodeDisplay, Separator, Text, useSporeColors } from 'ui/src' -import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' +import { AdaptiveWebModal, Button, Flex, Image, QRCodeDisplay, Separator, Text, useSporeColors } from 'ui/src' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { useTranslation } from 'uniswap/src/i18n' import { isWebAndroid, isWebIOS } from 'utilities/src/platform' @@ -21,13 +18,6 @@ export default function UniwalletModal() { const onLaunchedMobilePlatform = isWebIOS || isWebAndroid const open = !onLaunchedMobilePlatform && !!uri && connection.isPending - const uniswapWalletConnectConnector = useConnectorWithId( - CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, - { - shouldThrow: true, - }, - ) - useEffect(() => { function listener({ type, data }: { type: string; data?: unknown }) { if (type === 'display_uniswap_uri' && typeof data === 'string') { @@ -35,12 +25,12 @@ export default function UniwalletModal() { } } - uniswapWalletConnectConnector.emitter.on('message', listener) + window.addEventListener('display_uniswap_uri', listener) return () => { - uniswapWalletConnectConnector.emitter.off('message', listener) + window.removeEventListener('display_uniswap_uri', listener) } - }, [uniswapWalletConnectConnector.emitter]) + }, []) const close = useCallback(() => { connection?.reset() @@ -57,7 +47,7 @@ export default function UniwalletModal() { const colors = useSporeColors() return ( - + {t('account.drawer.modal.scan')} @@ -72,7 +62,6 @@ export default function UniwalletModal() { containerBackgroundColor={colors.surface1.val} encodedValue={uri} size={370} - eyeSize={140} > @@ -98,6 +87,6 @@ export default function UniwalletModal() { - + ) } diff --git a/apps/web/src/components/AccountDrawer/__snapshots__/index.test.tsx.snap b/apps/web/src/components/AccountDrawer/__snapshots__/index.test.tsx.snap index 00c838c56c0..7f3565a57c6 100644 --- a/apps/web/src/components/AccountDrawer/__snapshots__/index.test.tsx.snap +++ b/apps/web/src/components/AccountDrawer/__snapshots__/index.test.tsx.snap @@ -730,15 +730,22 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = ` class="c6" data-testid="wallet-modal" > -
+ +

+
Connect a wallet @@ -772,13 +779,13 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = ` class="c4" > Get Uniswap Wallet Available on iOS, Android, and Chrome @@ -811,13 +818,13 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = ` class="c4" > Uniswap Mobile Scan QR code to connect @@ -837,7 +844,7 @@ exports[`AccountDrawer tests AccountDrawer default styles 1`] = ` class="c24 c15" > Other wallets diff --git a/apps/web/src/components/AccountDrawer/shared.tsx b/apps/web/src/components/AccountDrawer/shared.tsx index d3fabdd97ed..cd7dabd9c3a 100644 --- a/apps/web/src/components/AccountDrawer/shared.tsx +++ b/apps/web/src/components/AccountDrawer/shared.tsx @@ -54,7 +54,7 @@ export function MenuItem({ {logo && logo} {label} - {isActive && } + {isActive && } ) } diff --git a/apps/web/src/components/AddressQRModal.tsx b/apps/web/src/components/AddressQRModal.tsx index 35aa53a0418..a82acdc18b8 100644 --- a/apps/web/src/components/AddressQRModal.tsx +++ b/apps/web/src/components/AddressQRModal.tsx @@ -10,6 +10,7 @@ import { ThemedText } from 'theme/components' import { AdaptiveWebModal, Flex, QRCodeDisplay, Text, useSporeColors } from 'ui/src' import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos' import { useAddressColorProps } from 'uniswap/src/features/address/color' +import { useOrderedChainIds } from 'uniswap/src/features/chains/hooks/useOrderedChainIds' import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/features/chains/types' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' import { Trans } from 'uniswap/src/i18n' @@ -26,6 +27,7 @@ export function AddressQRModal({ accountAddress }: { accountAddress: Address }) const { unitag } = useUnitagByAddress(accountAddress) const hasSecondaryIdentifier = ENSName || unitag?.username const addressColor = useAddressColorProps(accountAddress) + const orderedChainIds = useOrderedChainIds(SUPPORTED_CHAIN_IDS) const goBack = useCallback(() => { toggleModal() @@ -56,7 +58,6 @@ export function AddressQRModal({ accountAddress }: { accountAddress: Address }) color={addressColor} containerBackgroundColor={colors.surface1.val} size={QR_CODE_SIZE} - eyeSize={180} encodedValue={accountAddress!} > - + - + diff --git a/apps/web/src/components/AnimatedDropdown/index.test.tsx b/apps/web/src/components/AnimatedDropdown/index.test.tsx deleted file mode 100644 index 15313e68bdc..00000000000 --- a/apps/web/src/components/AnimatedDropdown/index.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import AnimatedDropdown from 'components/AnimatedDropdown' -import { render, screen, waitFor } from 'test-utils/render' - -describe('AnimatedDropdown', () => { - it('does not render children when closed', () => { - render(Body) - expect(screen.getByText('Body')).not.toBeVisible() - }) - - it('renders children when open', () => { - render(Body) - expect(screen.getByText('Body')).toBeVisible() - }) - - it('animates when open changes', async () => { - const { rerender } = render(Body) - - const body = screen.getByText('Body') - - expect(body).not.toBeVisible() - - rerender(Body) - expect(body).not.toBeVisible() - - // wait for React Spring animation to finish - await waitFor(() => { - expect(body).toBeVisible() - }) - }) -}) diff --git a/apps/web/src/components/AnimatedDropdown/index.tsx b/apps/web/src/components/AnimatedDropdown/index.tsx deleted file mode 100644 index ad136525b45..00000000000 --- a/apps/web/src/components/AnimatedDropdown/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useRef } from 'react' -import { UseSpringProps, animated, easings, useSpring } from 'react-spring' -import { TRANSITION_DURATIONS } from 'theme/styles' -import useResizeObserver from 'use-resize-observer' - -type AnimatedDropdownProps = React.PropsWithChildren<{ open: boolean; springProps?: UseSpringProps }> -/** - * @param open conditional to show content or hide - * @param springProps additional props to include in spring animation - * @returns Wrapper to smoothly hide and expand content - */ -export default function AnimatedDropdown({ open, springProps, children }: AnimatedDropdownProps) { - const wasOpen = useRef(open) - const { ref, height } = useResizeObserver() - - const props = useSpring({ - // On initial render, `height` will be undefined as ref has not been set yet. - // If the dropdown should be open, we fallback to `auto` to avoid flickering. - // Otherwise, we just animate between actual height (when open) and 0 (when closed). - height: open ? height ?? 'auto' : 0, - config: { - easing: open ? easings.easeInCubic : easings.easeOutCubic, - // Avoid animating if `open` is unchanged, so that nested AnimatedDropdowns don't stack and delay animations. - duration: open === wasOpen.current ? 0 : TRANSITION_DURATIONS.medium, - }, - onStart: () => { - wasOpen.current = open - }, - ...springProps, - }) - return ( - -
{children}
-
- ) -} diff --git a/apps/web/src/components/Banner/Outage/OutageBanner.tsx b/apps/web/src/components/Banner/Outage/OutageBanner.tsx index 2a460f9828c..87aa63050f1 100644 --- a/apps/web/src/components/Banner/Outage/OutageBanner.tsx +++ b/apps/web/src/components/Banner/Outage/OutageBanner.tsx @@ -5,7 +5,7 @@ import { useState } from 'react' import { Globe } from 'react-feather' import { ExternalLink, ThemedText } from 'theme/components' import { capitalize } from 'tsafe' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { UniverseChainId } from 'uniswap/src/features/chains/types' import { toGraphQLChain } from 'uniswap/src/features/chains/utils' import { Trans } from 'uniswap/src/i18n' diff --git a/apps/web/src/components/ChainConnectivityWarning.tsx b/apps/web/src/components/ChainConnectivityWarning.tsx index a6531da5ac9..00c734bdd92 100644 --- a/apps/web/src/components/ChainConnectivityWarning.tsx +++ b/apps/web/src/components/ChainConnectivityWarning.tsx @@ -4,11 +4,13 @@ import { useIsLandingPage } from 'hooks/useIsLandingPage' import { useIsNftPage } from 'hooks/useIsNftPage' import useMachineTimeMs from 'hooks/useMachineTime' import styled from 'lib/styled-components' -import { useMemo } from 'react' -import { AlertTriangle } from 'react-feather' -import { ExternalLink } from 'theme/components' +import { useMemo, useState } from 'react' +import { AlertTriangle, X } from 'react-feather' +import { ClickableTamaguiStyle, ExternalLink } from 'theme/components' +import { Flex, styled as tamaguiStyled } from 'ui/src' +import { iconSizes } from 'ui/src/theme' import { DEFAULT_MS_BEFORE_WARNING, getChainInfo } from 'uniswap/src/features/chains/chainInfo' -import { useEnabledChains } from 'uniswap/src/features/chains/hooks' +import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { UniverseChainId } from 'uniswap/src/features/chains/types' import { AVERAGE_L1_BLOCK_TIME_MS } from 'uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain' import { Trans } from 'uniswap/src/i18n' @@ -30,7 +32,6 @@ const TitleRow = styled.div` align-items: center; display: flex; justify-content: flex-start; - margin-bottom: 8px; ` const TitleText = styled.div` color: ${({ theme }) => theme.neutral1}; @@ -51,12 +52,17 @@ const Wrapper = styled.div` bottom: 16px; right: 16px; ` +const CloseButton = tamaguiStyled(X, { + ...ClickableTamaguiStyle, + size: iconSizes.icon20, +}) export function ChainConnectivityWarning() { const { chainId } = useAccount() const { defaultChainId } = useEnabledChains() const info = getChainInfo(chainId ?? defaultChainId) const label = info.label + const [hide, setHide] = useState(false) const isNftPage = useIsNftPage() const isLandingPage = useIsLandingPage() @@ -76,18 +82,21 @@ export function ChainConnectivityWarning() { ) const warning = Boolean(!!blockTime && machineTime - blockTime.mul(1000).toNumber() > waitMsBeforeWarning) - if (!warning || isNftPage || isLandingPage) { + if (!warning || isNftPage || isLandingPage || hide) { return null } return ( - - - - - - + + + + + + + + setHide(true)} /> + {chainId === UniverseChainId.Mainnet ? ( diff --git a/apps/web/src/components/Charts/ActiveLiquidityChart/ActiveLiquidityChart2.tsx b/apps/web/src/components/Charts/ActiveLiquidityChart/ActiveLiquidityChart2.tsx new file mode 100644 index 00000000000..9edb36aa2b0 --- /dev/null +++ b/apps/web/src/components/Charts/ActiveLiquidityChart/ActiveLiquidityChart2.tsx @@ -0,0 +1,139 @@ +import { AxisLeft } from 'components/Charts/ActiveLiquidityChart/AxisLeft' +import { Brush2 } from 'components/Charts/ActiveLiquidityChart/Brush2' +import { HorizontalArea } from 'components/Charts/ActiveLiquidityChart/HorizontalArea' +import { HorizontalLine } from 'components/Charts/ActiveLiquidityChart/HorizontalLine' +import { ChartEntry } from 'components/LiquidityChartRangeInput/types' +import { max as getMax, scaleLinear } from 'd3' +import { useEffect, useMemo } from 'react' +import { useSporeColors } from 'ui/src' + +const xAccessor = (d: ChartEntry) => d.activeLiquidity +const yAccessor = (d: ChartEntry) => d.price0 + +/** + * A horizontal version of the active liquidity area chart, which uses the standard + * x-y coordinate plane to show the data. However, note that the default use case (the range input) + * shows the data on the right, so by default the chart is flipped along both axes! + * + * Post-flip: + * - Bars grow (to the left) along the X axis to represent the active liquidity at a given price. + * - Bars are placed along the Y axis to represent price (i.e. bottom of chart is y=0 or the min price). + */ +export function ActiveLiquidityChart2({ + id = 'ActiveLiquidityChart2', + data: { series, current, min, max }, + dimensions: { width, height, contentWidth, axisLabelPaneWidth }, + brushDomain, + onBrushDomainChange, + disableBrushInteraction, +}: { + id?: string + data: { + series: ChartEntry[] + current: number + min?: number + max?: number + } + disableBrushInteraction?: boolean + dimensions: { width: number; height: number; contentWidth: number; axisLabelPaneWidth: number } + brushDomain?: [number, number] + onBrushDomainChange: (domain: [number, number], mode: string | undefined) => void +}) { + const colors = useSporeColors() + + const { xScale, yScale } = useMemo(() => { + const activeEntries = min && max ? series.filter((d) => d.price0 >= min && d.price0 <= max) : series + + // These linear scales map the data to non-flipped x-y coordinates! + // The flipping of the chart happens only with CSS below. + const scales = { + yScale: scaleLinear() + .domain([min, max] as number[]) + .range([0, height]), + xScale: scaleLinear() + .domain([0, getMax(activeEntries, xAccessor)] as number[]) + .range([axisLabelPaneWidth, axisLabelPaneWidth + contentWidth]), + } + + return scales + }, [min, max, series, height, axisLabelPaneWidth, contentWidth]) + + useEffect(() => { + if (!brushDomain) { + const [min, max] = yScale.domain() + const lowerBound = min + (max - min) * 0.2 + const upperBound = min + (max - min) * 0.8 + onBrushDomainChange([lowerBound, upperBound], undefined) + } + }, [brushDomain, onBrushDomainChange, yScale]) + + return ( + <> + + + + + + + {brushDomain && ( + // mask to highlight selected area + + + + )} + + + + + + + + + + + + + + + + ) +} diff --git a/apps/web/src/components/Charts/ActiveLiquidityChart/AxisLeft.tsx b/apps/web/src/components/Charts/ActiveLiquidityChart/AxisLeft.tsx new file mode 100644 index 00000000000..0fca86db9d7 --- /dev/null +++ b/apps/web/src/components/Charts/ActiveLiquidityChart/AxisLeft.tsx @@ -0,0 +1,82 @@ +import { NumberValue, ScaleLinear, axisLeft, Axis as d3Axis, select } from 'd3' +import styled from 'lib/styled-components' +import { useMemo } from 'react' + +const StyledGroup = styled.g` + line { + display: none; + } + + text { + color: ${({ theme }) => theme.neutral2}; + } +` + +const TEXT_Y_OFFSET = 10 + +const Axis = ({ + axisGenerator, + height, + yScale, +}: { + axisGenerator: d3Axis + height: number + yScale: ScaleLinear +}) => { + const axisRef = (axis: SVGGElement) => { + if (axis) { + select(axis) + .call(axisGenerator) + .call((g) => g.select('.domain').remove()) + .call((g) => + g.selectAll('text').attr('transform', function (d) { + const yCoordinate = yScale(d as number) + if (yCoordinate < TEXT_Y_OFFSET) { + return `translate(0, ${TEXT_Y_OFFSET}) scale(-1,-1)` + } + if (yCoordinate > height - TEXT_Y_OFFSET) { + return `translate(0, ${-TEXT_Y_OFFSET}) scale(-1,-1)` + } + return 'scale(-1, -1)' + }), + ) + } + } + + return +} + +export const AxisLeft = ({ + yScale, + offset = 0, + min, + current, + max, + height, +}: { + yScale: ScaleLinear + height: number + offset?: number + min?: number + current?: number + max?: number +}) => { + const tickValues = useMemo(() => { + const minCoordinate = min ? yScale(min) : undefined + const maxCoordinate = max ? yScale(max) : undefined + const currentCoordinate = current ? yScale(current) : undefined + if (minCoordinate && currentCoordinate && Math.abs(minCoordinate - currentCoordinate) < TEXT_Y_OFFSET) { + return [min, max].filter(Boolean) as number[] + } + if (maxCoordinate && currentCoordinate && Math.abs(maxCoordinate - currentCoordinate) < TEXT_Y_OFFSET) { + return [min, max].filter(Boolean) as number[] + } + return [min, current, max].filter(Boolean) as number[] + }, [current, max, min, yScale]) + + return ( + + + + ) +} diff --git a/apps/web/src/components/Charts/ActiveLiquidityChart/Brush2.tsx b/apps/web/src/components/Charts/ActiveLiquidityChart/Brush2.tsx new file mode 100644 index 00000000000..03eff58887f --- /dev/null +++ b/apps/web/src/components/Charts/ActiveLiquidityChart/Brush2.tsx @@ -0,0 +1,266 @@ +import { OffScreenHandleV2, brushHandleAccentPathV2, brushHandlePathV2 } from 'components/LiquidityChartRangeInput/svg' +import { BrushBehavior, D3BrushEvent, ScaleLinear, brushY, select } from 'd3' +import usePrevious from 'hooks/usePrevious' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useSporeColors } from 'ui/src' +import { useTranslation } from 'uniswap/src/i18n' + +// flips the handles draggers when close to the container edges +const FLIP_HANDLE_THRESHOLD_PX = 20 + +// margin to prevent tick snapping from putting the brush off screen +const BRUSH_EXTENT_MARGIN_PX = 2 + +/** + * Returns true if every element in `a` maps to the + * same pixel coordinate as elements in `b` + */ +const compare = (a: [number, number], b: [number, number], yScale: ScaleLinear): boolean => { + // normalize pixels to 1 decimals + const aNorm = a.map((y) => yScale(y).toFixed(1)) + const bNorm = b.map((y) => yScale(y).toFixed(1)) + return aNorm.every((v, i) => v === bNorm[i]) +} + +export const Brush2 = ({ + id, + yScale, + interactive, + brushExtent, + setBrushExtent, + hideHandles, + width, + height, + offset, +}: { + id: string + yScale: ScaleLinear + interactive: boolean + brushExtent: [number, number] + setBrushExtent: (extent: [number, number], mode: string | undefined) => void + width: number + height: number + offset: number + hideHandles?: boolean +}) => { + const colors = useSporeColors() + const brushRef = useRef(null) + const brushBehavior = useRef | null>(null) + + const { t } = useTranslation() + + // only used to drag the handles on brush for performance + const [localBrushExtent, setLocalBrushExtent] = useState<[number, number] | null>(brushExtent) + + const previousBrushExtent = usePrevious(brushExtent) + + const effectiveBrushWidth = width - offset + + const brushed = useCallback( + (event: D3BrushEvent) => { + const { type, selection, mode } = event + + if (!selection) { + setLocalBrushExtent(null) + return + } + + const scaled = (selection as [number, number]).map(yScale.invert) as [number, number] + + // avoid infinite render loop by checking for change + if (type === 'end' && !compare(brushExtent, scaled, yScale)) { + setBrushExtent(scaled, mode) + } + + setLocalBrushExtent(scaled) + }, + [yScale, brushExtent, setBrushExtent], + ) + + // keep local and external brush extent in sync + // i.e. snap to ticks on brush end + useEffect(() => { + setLocalBrushExtent(brushExtent) + }, [brushExtent]) + + // initialize the brush + useEffect(() => { + if (!brushRef.current) { + return + } + + brushBehavior.current = brushY() + .extent([ + [0, Math.max(0, yScale(0) + BRUSH_EXTENT_MARGIN_PX)], + [width, height - BRUSH_EXTENT_MARGIN_PX], + ]) + .handleSize(30) + .filter(() => interactive) + .on('brush end', brushed) + + brushBehavior.current(select(brushRef.current)) + + if (previousBrushExtent && compare(brushExtent, previousBrushExtent, yScale)) { + select(brushRef.current) + .transition() + .call(brushBehavior.current.move as any, brushExtent.map(yScale)) + } + + // brush linear gradient + select(brushRef.current) + .selectAll('.selection') + .attr('stroke', 'none') + .attr('fill-opacity', '0.1') + .attr('fill', `url(#${id}-gradient-selection)`) + }, [brushExtent, brushed, id, height, interactive, previousBrushExtent, yScale, offset, effectiveBrushWidth, width]) + + // respond to yScale changes only + useEffect(() => { + if (!brushRef.current || !brushBehavior.current) { + return + } + + brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(yScale) as any) + }, [brushExtent, yScale]) + + // variables to help render the SVGs + const flipNorthHandle = localBrushExtent && yScale(localBrushExtent[0]) > FLIP_HANDLE_THRESHOLD_PX + const flipSouthHandle = localBrushExtent && yScale(localBrushExtent[1]) > height - FLIP_HANDLE_THRESHOLD_PX + + const showNorthArrow = localBrushExtent && (yScale(localBrushExtent[0]) < 0 || yScale(localBrushExtent[1]) < 0) + const showSouthArrow = + localBrushExtent && (yScale(localBrushExtent[0]) > height || yScale(localBrushExtent[1]) > height) + + const northHandleInView = + localBrushExtent && yScale(localBrushExtent[0]) >= 0 && yScale(localBrushExtent[0]) <= height + const southHandleInView = + localBrushExtent && yScale(localBrushExtent[1]) >= 0 && yScale(localBrushExtent[1]) <= height + + return useMemo( + () => ( + <> + + + + + + + {/* clips at exactly the svg area */} + + + + + + {/* will host the d3 brush */} + + + {/* custom brush handles */} + {localBrushExtent && !hideHandles && ( + <> + {northHandleInView ? ( + + + + + + + ) : null} + + {southHandleInView ? ( + + + + + + + ) : null} + + {showNorthArrow && ( + + + {!showSouthArrow && ( + + {t('range.outOfView')} + + )} + + )} + {showSouthArrow && ( + + + + {t('range.outOfView')} + + + )} + + )} + + ), + [ + id, + colors, + offset, + effectiveBrushWidth, + height, + localBrushExtent, + hideHandles, + northHandleInView, + yScale, + flipNorthHandle, + interactive, + southHandleInView, + flipSouthHandle, + showNorthArrow, + width, + t, + showSouthArrow, + ], + ) +} diff --git a/apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalArea.tsx b/apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalArea.tsx new file mode 100644 index 00000000000..d507f3e3e99 --- /dev/null +++ b/apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalArea.tsx @@ -0,0 +1,57 @@ +import { ChartEntry } from 'components/LiquidityChartRangeInput/types' +import { ScaleLinear } from 'd3' +import styled from 'lib/styled-components' + +const Bar = styled.rect<{ fill?: string }>` + opacity: 0.5; + stroke: ${({ fill, theme }) => fill ?? theme.accent1}; + fill: ${({ fill, theme }) => fill ?? theme.accent1}; +` + +export const HorizontalArea = ({ + series, + xScale, + yScale, + xValue, + yValue, + fill, + brushDomain, + selectedFill, + containerHeight, +}: { + series: ChartEntry[] + xScale: ScaleLinear + yScale: ScaleLinear + xValue: (d: ChartEntry) => number + yValue: (d: ChartEntry) => number + brushDomain?: [number, number] + containerHeight: number + fill?: string + selectedFill?: string +}) => { + return ( + <> + {series + .filter((d) => { + const value = yScale(yValue(d)) + return value > 0 && value <= containerHeight + }) + .map((d, i) => { + const price = yValue(d) + const isInDomain = brushDomain && price >= brushDomain[0] && price <= brushDomain[1] + return ( + + ) + })} + + ) +} diff --git a/apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalLine.tsx b/apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalLine.tsx new file mode 100644 index 00000000000..17fb1898ff7 --- /dev/null +++ b/apps/web/src/components/Charts/ActiveLiquidityChart/HorizontalLine.tsx @@ -0,0 +1,36 @@ +import { ScaleLinear } from 'd3' +import styled from 'lib/styled-components' + +const StyledLine = styled.line` + opacity: 0.5; + stroke-width: 1; + stroke: ${({ theme }) => theme.neutral2}; + stroke-dasharray: '2, 5'; + fill: none; +` + +export const HorizontalLine = ({ + value, + yScale, + xScale, + width, +}: { + value: number + yScale: ScaleLinear + xScale: ScaleLinear + width: number +}) => { + const lineStart = xScale(0) + if (isNaN(lineStart)) { + return null + } + return ( + + ) +} diff --git a/apps/web/src/components/Charts/BandsIndicator/bands-indicator.ts b/apps/web/src/components/Charts/BandsIndicator/bands-indicator.ts new file mode 100644 index 00000000000..425f862370f --- /dev/null +++ b/apps/web/src/components/Charts/BandsIndicator/bands-indicator.ts @@ -0,0 +1,160 @@ +import { ClosestTimeIndexFinder } from 'components/Charts/BandsIndicator/helpers/closest-index' +import { UpperLowerInRange } from 'components/Charts/BandsIndicator/helpers/min-max-in-range' +import { cloneReadonly } from 'components/Charts/BandsIndicator/helpers/simple-clone' +import { PluginBase } from 'components/Charts/BandsIndicator/plugin-base' +import { CanvasRenderingTarget2D } from 'fancy-canvas' +import { + Coordinate, + DataChangedScope, + ISeriesPrimitive, + ISeriesPrimitivePaneRenderer, + ISeriesPrimitivePaneView, + SeriesAttachedParameter, + SeriesDataItemTypeMap, + SeriesType, + Time, +} from 'lightweight-charts' + +interface BandRendererData { + x: Coordinate | number + upper: Coordinate | number + lower: Coordinate | number +} + +class BandsIndicatorPaneRenderer implements ISeriesPrimitivePaneRenderer { + _viewData: BandViewData + constructor(data: BandViewData) { + this._viewData = data + } + draw() {} + drawBackground(target: CanvasRenderingTarget2D) { + const points: BandRendererData[] = this._viewData.data + target.useBitmapCoordinateSpace((scope) => { + const ctx = scope.context + ctx.scale(scope.horizontalPixelRatio, scope.verticalPixelRatio) + + ctx.strokeStyle = this._viewData.options.lineColor + ctx.lineWidth = this._viewData.options.lineWidth + ctx.beginPath() + const region = new Path2D() + const lines = new Path2D() + region.moveTo(points[0]?.x, points[0]?.upper) + lines.moveTo(points[0]?.x, points[0]?.upper) + for (const point of points) { + region.lineTo(point?.x, point.upper) + lines.lineTo(point?.x, point.upper) + } + const end = points.length - 1 + region.lineTo(points[end]?.x, points[end]?.lower) + lines.moveTo(points[end]?.x, points[end]?.lower) + for (let i = points.length - 2; i >= 0; i--) { + region.lineTo(points[i]?.x, points[i]?.lower) + lines.lineTo(points[i]?.x, points[i]?.lower) + } + region.lineTo(points[0]?.x, points[0]?.upper) + region.closePath() + ctx.stroke(lines) + ctx.fillStyle = this._viewData.options.fillColor + ctx.fill(region) + }) + } +} + +interface BandViewData { + data: BandRendererData[] + options: Required +} + +class BandsIndicatorPaneView implements ISeriesPrimitivePaneView { + _source: BandsIndicator + _data: BandViewData + + constructor(source: BandsIndicator) { + this._source = source + this._data = { + data: [], + options: this._source._options, + } + } + + update() { + const series = this._source.series + const timeScale = this._source.chart.timeScale() + this._data.data = this._source._bandsData.map((d) => { + return { + x: timeScale.timeToCoordinate(d.time) ?? -100, + upper: series.priceToCoordinate(d.upper) ?? -100, + lower: series.priceToCoordinate(d.lower) ?? -100, + } + }) + } + + renderer() { + return new BandsIndicatorPaneRenderer(this._data) + } +} + +interface BandData { + time: Time + upper: number + lower: number +} + +interface BandsIndicatorOptions { + lineColor: string + fillColor: string + lineWidth: number + upperValue: number + lowerValue: number +} + +export class BandsIndicator extends PluginBase implements ISeriesPrimitive