From c0bbcf59f04d07c6e866596921173214663bea10 Mon Sep 17 00:00:00 2001 From: Uniswap Labs Service Account Date: Wed, 17 Apr 2024 17:53:41 +0000 Subject: [PATCH] ci(release): publish latest release --- RELEASE | 50 +- VERSION | 2 +- apps/mobile/README.md | 3 +- apps/mobile/ios/Podfile.lock | 8 +- apps/mobile/jest.config.js | 4 - apps/mobile/package.json | 1 - apps/mobile/src/app/App.tsx | 20 +- .../app/MobileWalletNavigationProvider.tsx | 45 +- apps/mobile/src/app/migrations.test.ts | 8 + apps/mobile/src/app/migrations.ts | 7 + .../src/app/modals/AccountSwitcherModal.tsx | 4 +- .../src/app/modals/ExperimentsModal.tsx | 126 +- .../src/app/modals/TransferTokenModal.tsx | 4 +- apps/mobile/src/app/navigation/hooks.ts | 2 +- apps/mobile/src/app/navigation/navigation.tsx | 4 +- apps/mobile/src/app/schema.ts | 15 +- apps/mobile/src/app/store.ts | 2 +- .../QRCodeScanner/QRCodeScanner.tsx | 9 +- .../components/RecipientSelect/hooks.test.ts | 1 - .../ModalWithOverlay/ModalWithOverlay.tsx | 4 +- .../WalletConnect/RequestModal/HeaderText.tsx | 8 +- .../RequestModal/UwULinkErc20SendModal.tsx | 137 ++ .../WalletConnectRequestModal.tsx | 48 +- .../WalletConnectRequestModalContent.tsx | 7 +- .../ScanSheet/SwitchAccountOption.tsx | 4 +- .../ScanSheet/WalletConnectModal.tsx | 104 +- .../WalletConnect/ScanSheet/util.ts | 86 +- .../explore/search/SearchEmptySection.tsx | 4 +- .../src/components/fiatOnRamp/CtaButton.tsx | 45 +- .../forceUpgrade/ForceUpgradeModal.tsx | 4 +- apps/mobile/src/components/home/NftsTab.tsx | 2 +- .../src/components/home/WalletEmptyState.tsx | 4 +- .../src/components/unitags/ChooseNftModal.tsx | 2 +- .../src/data/usePersistedApolloClient.tsx | 4 +- .../mobile/src/features/deepLinking/README.md | 2 +- .../deepLinking/handleDeepLinkSaga.ts | 2 +- .../externalProfile/ProfileHeader.tsx | 4 +- .../features/fiatOnRamp/FiatOnRampModal.tsx | 9 +- apps/mobile/src/features/onboarding/hooks.ts | 4 +- apps/mobile/src/features/telemetry/types.ts | 9 +- .../transactions/transfer/TransferFlow.tsx | 12 +- .../features/unitags/ClaimUnitagScreen.tsx | 2 +- .../unitags/EditUnitagProfileScreen.tsx | 4 +- apps/mobile/src/features/wallet/hooks.ts | 4 +- .../walletConnect/signWcRequestSaga.ts | 4 +- .../walletConnect/walletConnectSlice.ts | 18 +- apps/mobile/src/polyfills/index.ts | 2 +- .../src/screens/FiatOnRampConnecting.tsx | 2 +- apps/mobile/src/screens/HomeScreen.tsx | 4 +- .../Import/RestoreCloudBackupScreen.tsx | 4 +- apps/mobile/src/screens/NFTItemScreen.tsx | 4 +- .../src/screens/Onboarding/LandingScreen.tsx | 4 +- .../Onboarding/QRAnimation/QRAnimation.tsx | 4 +- apps/mobile/src/screens/SettingsScreen.tsx | 4 +- apps/mobile/src/screens/SettingsWallet.tsx | 4 +- .../mobile/src/screens/SettingsWalletEdit.tsx | 4 +- .../e2e/mini-portfolio/uniTags.test.ts | 12 +- apps/web/cypress/e2e/permit2.test.ts | 3 +- apps/web/cypress/e2e/wallet-dropdown.test.ts | 2 +- apps/web/cypress/support/commands.ts | 2 +- apps/web/functions/[[index]].ts | 10 +- .../functions/components/metaTagInjector.ts | 2 + .../web/functions/explore/tokens/[[index]].ts | 10 +- apps/web/functions/nfts/asset/[[index]].ts | 8 +- apps/web/functions/nfts/collection/[index].ts | 8 +- apps/web/functions/utils/getRequest.ts | 18 - apps/web/functions/utils/transformResponse.ts | 23 + apps/web/package.json | 5 +- apps/web/public/csp.json | 43 +- .../AccountDrawer/AuthenticatedHeader.tsx | 1 + .../Activity/OffchainActivityModal.test.tsx | 19 +- .../Activity/OffchainActivityModal.tsx | 2 +- .../__snapshots__/parseRemote.test.tsx.snap | 97 +- .../Activity/fixtures/activity.ts | 63 - .../Activity/parseRemote.test.tsx | 13 +- .../MiniPortfolio/Activity/parseRemote.tsx | 132 +- .../MiniPortfolio/Activity/types.ts | 1 + .../Limits/LimitDetailActivityRow.test.tsx | 17 +- .../Limits/LimitDetailActivityRow.tsx | 24 +- .../MiniPortfolio/Limits/LimitsMenu.test.tsx | 16 +- .../LimitDetailActivityRow.test.tsx.snap | 4 +- .../__snapshots__/LimitsMenu.test.tsx.snap | 96 +- .../MiniPortfolio/Pools/index.tsx | 2 +- .../MiniPortfolio/PortfolioLogo.test.tsx | 2 + .../MiniPortfolio/PortfolioLogo.tsx | 36 +- .../MiniPortfolio/Tokens/index.tsx | 9 +- .../AccountDrawer/MiniPortfolio/constants.tsx | 15 +- .../components/AccountDrawer/SettingsMenu.tsx | 4 +- .../src/components/AccountDrawer/Status.tsx | 44 +- .../src/components/AccountDrawer/index.tsx | 3 + .../components/Banner/Outage/OutageBanner.tsx | 3 +- apps/web/src/components/Charts/ChartModel.tsx | 3 + .../ConfirmSwapModal/Error.test.tsx | 2 + .../ConfirmSwapModal/Pending.test.tsx | 2 + .../LimitPriceInputPanel.test.tsx | 4 +- .../FeatureFlagModal/FeatureFlagModal.tsx | 8 +- .../src/components/Identicon/StatusIcon.tsx | 6 +- apps/web/src/components/Identicon/index.tsx | 2 +- apps/web/src/components/Logo/AssetLogo.tsx | 8 +- .../src/components/Logo/QueryTokenLogo.tsx | 2 +- .../src/components/NavBar/SuggestionRow.tsx | 15 +- .../Pools/PoolDetails/PoolDetailsHeader.tsx | 65 +- .../PoolDetails/PoolDetailsLink.test.tsx | 2 + .../PoolDetailsPositionTable.test.tsx | 3 +- .../PoolDetails/PoolDetailsPositionsTable.tsx | 18 +- .../PoolDetails/PoolDetailsStats.test.tsx | 15 +- .../PoolDetailsStatsButtons.test.tsx | 2 + .../PoolDetails/PoolDetailsStatsButtons.tsx | 8 +- .../PoolDetailsStatsButtons.test.tsx.snap | 120 +- .../__snapshots__/PoolTable.test.tsx.snap | 2 +- .../src/components/Popups/PopupContent.tsx | 2 +- .../SearchModal/CommonBases.test.tsx | 2 + .../components/SearchModal/CommonBases.tsx | 10 +- .../SearchModal/CurrencyList/index.tsx | 4 +- .../components/SearchModal/CurrencySearch.tsx | 4 +- .../SearchModal/useCurrencySearchResults.ts | 5 +- .../Table/__snapshots__/styled.test.tsx.snap | 6 +- apps/web/src/components/Table/styled.test.tsx | 2 - apps/web/src/components/Table/styled.tsx | 9 +- .../TokenSafety/TokenSafetyIcon.tsx | 7 +- .../TokenSafety/TokenSafetyLabel.tsx | 4 +- apps/web/src/components/TokenSafety/index.tsx | 12 +- .../TokenDetails/TokenDescription.test.tsx | 9 +- .../TokenDetails/TokenDetailsHeader.tsx | 4 +- .../components/Tokens/TokenDetails/index.tsx | 4 +- .../tables/TokenDetailsPoolsTable.test.tsx | 2 + .../TokenDetailsPoolsTable.test.tsx.snap | 2 +- .../WalletModal/useOrderedConnections.test.ts | 4 +- .../WalletModal/useOrderedConnections.tsx | 4 +- .../web/src/components/Web3Provider/index.tsx | 4 +- .../addLiquidity/OutOfSyncWarning.tsx | 18 +- .../components/claim/AddressClaimModal.tsx | 16 +- .../src/components/swap/SwapHeader.test.tsx | 4 +- apps/web/src/components/swap/SwapHeader.tsx | 4 +- .../src/components/swap/SwapLineItem.test.tsx | 2 + .../src/components/swap/SwapPreview.test.tsx | 2 + .../swap/UnsupportedCurrencyFooter.test.tsx | 2 +- apps/web/src/constants/lists.ts | 2 +- apps/web/src/constants/supportArticles.ts | 1 + apps/web/src/constants/tokenLogoLookup.ts | 43 - ...okenSaftey.test.ts => tokenSafety.test.ts} | 0 apps/web/src/constants/tokenSafety.tsx | 74 +- apps/web/src/constants/tokenSafetyLookup.ts | 63 - apps/web/src/dev/DevFlagsBox.tsx | 2 +- .../dynamicConfig/quickRouteChains.ts | 4 +- .../src/featureFlags/flags/outageBanner.ts | 4 +- apps/web/src/featureFlags/index.tsx | 13 +- .../data/apollo/AssetActivityProvider.tsx | 4 +- .../apollo/TokenBalancesProvider.test.tsx | 10 +- .../data/apollo/TokenBalancesProvider.tsx | 4 +- apps/web/src/graphql/data/apollo/client.ts | 2 +- apps/web/src/graphql/data/types.test.ts | 22 +- apps/web/src/graphql/data/types.ts | 8 +- apps/web/src/graphql/thegraph/schema.graphql | 6 + apps/web/src/hooks/Tokens.test.ts | 2 +- apps/web/src/hooks/Tokens.ts | 23 +- apps/web/src/hooks/useAssetLogoSource.ts | 97 -- apps/web/src/hooks/useColor.ts | 9 +- apps/web/src/hooks/useENSAvatar.ts | 19 +- apps/web/src/hooks/useEthersProvider.ts | 29 + apps/web/src/hooks/useEthersSigner.ts | 25 + ...seFilterPossiblyMaliciousPositions.test.ts | 67 +- apps/web/src/hooks/useNetworkSupportsV2.ts | 4 +- .../src/hooks/useTokenWarningColor.test.ts | 14 +- apps/web/src/hooks/useTokenWarningColor.ts | 22 +- apps/web/src/hooks/useUSDPrice.ts | 2 +- apps/web/src/hooks/useUniswapWalletOptions.ts | 4 +- apps/web/src/index.tsx | 4 +- .../hooks/routing/useRoutingAPIArguments.ts | 4 +- .../src/nft/components/bag/BagFooter.test.tsx | 4 - .../src/nft/components/layout/Checkbox.tsx | 2 +- apps/web/src/nft/pages/collection/index.tsx | 2 +- apps/web/src/pages/App.tsx | 1 - apps/web/src/pages/Landing/index.tsx | 4 +- apps/web/src/pages/Pool/PositionPage.test.tsx | 13 +- .../Swap/Send/NewAddressSpeedBump.test.tsx | 6 +- .../pages/Swap/Send/NewAddressSpeedBump.tsx | 4 +- .../Swap/Send/SendCurrencyInputForm.test.tsx | 11 + .../src/pages/Swap/Send/SendRecipientForm.tsx | 24 +- .../pages/Swap/Send/SendReviewModal.test.tsx | 2 + .../src/pages/Swap/Send/SendReviewModal.tsx | 4 +- apps/web/src/pages/TokenDetails/index.tsx | 4 +- apps/web/src/setupTests.ts | 4 +- .../state/activity/{ => polling}/orders.ts | 14 +- .../activity/{ => polling}/retry.test.ts | 0 .../src/state/activity/{ => polling}/retry.ts | 0 .../{ => polling}/transactions.test.ts | 0 .../activity/{ => polling}/transactions.ts | 61 +- apps/web/src/state/activity/subscription.ts | 43 + apps/web/src/state/activity/types.ts | 26 + apps/web/src/state/activity/updater.tsx | 82 +- apps/web/src/state/activity/utils.ts | 15 + apps/web/src/state/limit/hooks.ts | 4 +- apps/web/src/state/lists/reducer.test.ts | 14 - apps/web/src/state/lists/reducer.ts | 2 - apps/web/src/state/lists/updater.ts | 9 +- apps/web/src/state/routing/usePreviewTrade.ts | 4 +- .../__snapshots__/parseRemote.test.ts.snap | 79 ++ apps/web/src/state/signatures/fixtures.ts | 109 ++ apps/web/src/state/signatures/hooks.ts | 4 +- .../src/state/signatures/parseRemote.test.ts | 17 + apps/web/src/state/signatures/parseRemote.ts | 86 ++ apps/web/src/state/signatures/types.ts | 3 +- apps/web/src/state/swap/hooks.tsx | 3 +- apps/web/src/test-utils/constants.ts | 90 +- apps/web/src/test-utils/pools/fixtures.ts | 16 + apps/web/src/test-utils/tokens/mocks.ts | 60 + apps/web/src/theme/components/text.tsx | 3 - apps/web/src/tracing/index.ts | 1 - apps/web/src/utils/formatNumbers.test.ts | 4 +- apps/web/src/utils/formatNumbers.ts | 4 +- apps/web/src/utils/getInitialLogoURL.ts | 26 + config/jest-presets/jest/jest-preset.js | 1 + config/jest-presets/jest/setup.js | 4 +- config/jest-presets/package.json | 1 + package.json | 2 +- packages/ui/README.md | 9 +- packages/ui/package.json | 1 + packages/ui/src/assets/icons/receive-alt.svg | 3 + packages/ui/src/components/button/Button.tsx | 6 +- .../ui/src/components/icons/ReceiveAlt.tsx | 19 + packages/ui/src/components/icons/index.ts | 1 + packages/ui/src/components/text/Text.tsx | 7 +- .../text/useEnableFontScaling.native.tsx | 8 + .../components/text/useEnableFontScaling.tsx | 8 + packages/ui/src/scripts/componentize-icons.ts | 354 +++--- packages/ui/src/theme/fonts.ts | 26 +- .../graphql/uniswap-data-api/queries.graphql | 13 +- .../graphql/uniswap-data-api/schema.graphql | 54 +- .../uniswap-data-api/web/SimpleToken.graphql | 40 +- .../uniswap-data-api/web/activity.graphql | 13 +- .../graphql/uniswap-data-api/web/pool.graphql | 46 +- .../uniswap-data-api/web/portfolios.graphql | 18 +- .../uniswap-data-api/web/search.graphql | 32 +- .../uniswap-data-api/web/topPools.graphql | 8 +- .../src/features/experiments/constants.ts | 25 - .../uniswap/src/features/experiments/hooks.ts | 41 - .../experiments/statsig/statsig.native.ts | 8 - .../features/experiments/statsig/statsig.ts | 8 - .../{experiments => statsig}/configs.ts | 2 + .../uniswap/src/features/statsig/constants.ts | 2 + .../src/features/statsig/experiments.ts | 46 + .../{experiments => statsig}/flags.ts | 0 .../uniswap/src/features/statsig/hooks.ts | 76 ++ .../features/statsig/sdk/statsig.native.ts | 13 + .../src/features/statsig/sdk/statsig.ts | 13 + .../src/i18n/locales/source/en-US.json | 13 +- packages/wallet/package.json | 1 - .../__snapshots__/BaseCard.test.tsx.snap | 8 +- .../CurrencyLogo/LogoWithTxStatus.tsx | 4 +- .../src/components/CurrencyLogo/TokenLogo.tsx | 20 +- .../src/components/QRCodeScanner/QRCode.tsx | 4 +- .../WalletConnect/DappIconPlaceholder.tsx | 4 +- .../WalletPreviewCard.test.tsx.snap | 2 +- .../src/components/accounts/AccountIcon.tsx | 4 +- .../AccountDetails.test.tsx.snap | 8 +- .../DisplayNameText.test.tsx.snap | 8 +- .../components/modals/ActionSheetModal.tsx | 2 +- .../__snapshots__/NetworkFee.test.tsx.snap | 10 +- .../__snapshots__/NetworkPill.test.tsx.snap | 6 +- .../components/{NFT => nfts}/NFTTransfer.tsx | 0 .../wallet/src/components/nfts}/NftView.tsx | 4 +- .../__snapshots__/LearnMoreLink.test.tsx.snap | 2 +- .../text/__snapshots__/Pill.test.tsx.snap | 6 +- .../RelativeChange.test.tsx.snap | 6 +- .../src/contexts/WalletNavigationContext.tsx | 53 +- packages/wallet/src/features/auth/saga.ts | 8 +- .../src/features/fiatCurrency/conversion.ts | 4 +- .../wallet/src/features/fiatCurrency/hooks.ts | 4 +- .../wallet/src/features/fiatOnRamp/api.ts | 8 + .../wallet/src/features/gas/adjustGasFee.ts | 73 +- packages/wallet/src/features/language/saga.ts | 4 +- .../src/features/nfts/useNftContextMenu.tsx | 4 +- .../components/DappConnectedNotification.tsx | 26 + .../DappDisconnectedNotification.tsx | 26 + .../notifications/notificationWatcherSaga.ts | 2 +- .../src/features/notifications/types.ts | 14 +- .../portfolio/useTokenContextMenu.tsx | 20 +- .../src/features/search/SearchTextInput.tsx | 1 + .../SummaryItems/TransactionActionsModal.tsx | 5 +- .../TransactionHistoryUpdater.test.tsx | 1 - .../TransactionReview/TransactionReview.tsx | 2 +- .../extractFiatOnRampTransactionDetails.ts | 2 + .../features/transactions/swap/SwapFlow.tsx | 4 +- .../features/transactions/swap/customRpc.ts | 4 +- .../modals/settings/SwapSettingsModal.tsx | 4 +- .../features/transactions/swap/swapSaga.ts | 4 +- .../swap/trade/hooks/useDerivedSwapInfo.ts | 4 +- .../swap/trade/hooks/useSetTradeSlippage.ts | 49 +- .../transactionState/transactionState.test.ts | 4 +- .../transactionState/transactionState.ts | 5 +- .../transactions/transactionWatcherSaga.ts | 50 +- .../transfer/TransferTokenForm.tsx | 2 +- .../transfer/getSendPrefilledState.ts | 41 + .../hooks/useTransferTransactionRequest.ts | 2 +- packages/wallet/src/features/unitags/hooks.ts | 4 +- .../wallet/import/importAccountSaga.test.ts | 3 - .../wallet/import/importAccountSaga.ts | 2 - .../wallet/src/features/wallet/slice.test.ts | 1 - packages/wallet/src/features/wallet/slice.ts | 10 - .../src/features/walletConnect/types.ts | 37 +- packages/wallet/src/telemetry/constants.ts | 1 + packages/wallet/src/telemetry/types.ts | 4 +- packages/wallet/src/utils/persistedStorage.ts | 2 +- scripts/check-modified-files.sh | 4 +- yarn.lock | 1104 +++++++++++++++-- 306 files changed, 4004 insertions(+), 2141 deletions(-) create mode 100644 apps/mobile/src/components/WalletConnect/RequestModal/UwULinkErc20SendModal.tsx create mode 100644 apps/web/functions/utils/transformResponse.ts delete mode 100644 apps/web/src/constants/tokenLogoLookup.ts rename apps/web/src/constants/{tokenSaftey.test.ts => tokenSafety.test.ts} (100%) delete mode 100644 apps/web/src/constants/tokenSafetyLookup.ts delete mode 100644 apps/web/src/hooks/useAssetLogoSource.ts create mode 100644 apps/web/src/hooks/useEthersProvider.ts create mode 100644 apps/web/src/hooks/useEthersSigner.ts rename apps/web/src/state/activity/{ => polling}/orders.ts (90%) rename apps/web/src/state/activity/{ => polling}/retry.test.ts (100%) rename apps/web/src/state/activity/{ => polling}/retry.ts (100%) rename apps/web/src/state/activity/{ => polling}/transactions.test.ts (100%) rename apps/web/src/state/activity/{ => polling}/transactions.ts (77%) create mode 100644 apps/web/src/state/activity/subscription.ts create mode 100644 apps/web/src/state/activity/types.ts create mode 100644 apps/web/src/state/activity/utils.ts create mode 100644 apps/web/src/state/signatures/__snapshots__/parseRemote.test.ts.snap create mode 100644 apps/web/src/state/signatures/fixtures.ts create mode 100644 apps/web/src/state/signatures/parseRemote.test.ts create mode 100644 apps/web/src/state/signatures/parseRemote.ts create mode 100644 apps/web/src/test-utils/tokens/mocks.ts create mode 100644 apps/web/src/utils/getInitialLogoURL.ts create mode 100644 packages/ui/src/assets/icons/receive-alt.svg create mode 100644 packages/ui/src/components/icons/ReceiveAlt.tsx create mode 100644 packages/ui/src/components/text/useEnableFontScaling.native.tsx create mode 100644 packages/ui/src/components/text/useEnableFontScaling.tsx delete mode 100644 packages/uniswap/src/features/experiments/constants.ts delete mode 100644 packages/uniswap/src/features/experiments/hooks.ts delete mode 100644 packages/uniswap/src/features/experiments/statsig/statsig.native.ts delete mode 100644 packages/uniswap/src/features/experiments/statsig/statsig.ts rename packages/uniswap/src/features/{experiments => statsig}/configs.ts (94%) create mode 100644 packages/uniswap/src/features/statsig/constants.ts create mode 100644 packages/uniswap/src/features/statsig/experiments.ts rename packages/uniswap/src/features/{experiments => statsig}/flags.ts (100%) create mode 100644 packages/uniswap/src/features/statsig/hooks.ts create mode 100644 packages/uniswap/src/features/statsig/sdk/statsig.native.ts create mode 100644 packages/uniswap/src/features/statsig/sdk/statsig.ts rename packages/wallet/src/components/{NFT => nfts}/NFTTransfer.tsx (100%) rename {apps/mobile/src/components/NFT => packages/wallet/src/components/nfts}/NftView.tsx (93%) rename apps/mobile/src/features/nfts/hooks.ts => packages/wallet/src/features/nfts/useNftContextMenu.tsx (97%) create mode 100644 packages/wallet/src/features/notifications/components/DappConnectedNotification.tsx create mode 100644 packages/wallet/src/features/notifications/components/DappDisconnectedNotification.tsx create mode 100644 packages/wallet/src/features/transactions/transfer/getSendPrefilledState.ts diff --git a/RELEASE b/RELEASE index 0dfa5bd3086..9cd7811bd87 100644 --- a/RELEASE +++ b/RELEASE @@ -1,6 +1,6 @@ IPFS hash of the deployment: -- CIDv0: `QmXAfrFgiMF3naNbfoknu5e6H1ussSDJbvb1C1Paw5Ugpi` -- CIDv1: `bafybeiedfku63hfv4tr7zi3kx357dkdjvcoxp4wfv5i75y3qfabtwo3eym` +- CIDv0: `QmbEmY4ebbPrQirzx9PNZGKqyjK7pgUMVce2F2tMtJHbJ5` +- CIDv1: `bafybeif7uyvbcyhd5ib6xkikxoyvm4voodg4pgagjckny4m52o7qtclpsy` The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). @@ -10,15 +10,51 @@ You can also access the Uniswap Interface from an IPFS gateway. Your Uniswap settings are never remembered across different URLs. IPFS gateways: -- https://bafybeiedfku63hfv4tr7zi3kx357dkdjvcoxp4wfv5i75y3qfabtwo3eym.ipfs.dweb.link/ -- https://bafybeiedfku63hfv4tr7zi3kx357dkdjvcoxp4wfv5i75y3qfabtwo3eym.ipfs.cf-ipfs.com/ -- [ipfs://QmXAfrFgiMF3naNbfoknu5e6H1ussSDJbvb1C1Paw5Ugpi/](ipfs://QmXAfrFgiMF3naNbfoknu5e6H1ussSDJbvb1C1Paw5Ugpi/) +- https://bafybeif7uyvbcyhd5ib6xkikxoyvm4voodg4pgagjckny4m52o7qtclpsy.ipfs.dweb.link/ +- https://bafybeif7uyvbcyhd5ib6xkikxoyvm4voodg4pgagjckny4m52o7qtclpsy.ipfs.cf-ipfs.com/ +- [ipfs://QmbEmY4ebbPrQirzx9PNZGKqyjK7pgUMVce2F2tMtJHbJ5/](ipfs://QmbEmY4ebbPrQirzx9PNZGKqyjK7pgUMVce2F2tMtJHbJ5/) -### 5.23.5 (2024-04-15) +## 5.24.0 (2024-04-17) + + +### Features + +* **web:** [wagmi] Add viem to ethers adapters (#7237) 276faae +* **web:** deprecate token logo lookups (#6921) 0ade412 +* **web:** deprecate+delete token safety lookups (#7132) b4642e9 +* **web:** update signatures from subscription (#7389) f867870 ### Bug Fixes -* **web:** parse native MATIC correctly (#7523) 12a6205 +* **web:** allow TrustWallet nodes in CSP (#7515) 0a5b28d +* **web:** change background color of LP warning banner (#7491) f543ab0 +* **web:** change warning icon color; add learn more link (#7483) 352be3b +* **web:** checkbox color on open-limits drawer (#7410) 05dfaed +* **web:** dismiss chart tooltip when clicking outside of chart (#7285) 3d97a14 +* **web:** ellipsis on unitag text in side drawer (#7386) 471661c +* **web:** fix cypress tests (#7347) e54ceb9 +* **web:** fix icon cutoff (#7583) a01e106 +* **web:** fix translations with JSX rendering as [object Object] (#7336) 67df76c +* **web:** fix uniswapx e2e tests (#7458) c6de35e +* **web:** fix x-chain token logos (#7374) 4367e3a +* **web:** functions pass-through (#7338) ace3062 +* **web:** make sure SimpleToken hits the cache in all cases (#7488) 1be2954 +* **web:** parse matic correctly from gql response (#7519) 800eae5 +* **web:** remove assets repo fallback for all tokens (#7476) 8e2142c +* **web:** Send crashing on useENSAvatar while disconnected (#7595) 131c6a3 +* **web:** set usePoolData errorPolicy to all (#7466) 55e3e8a +* **web:** SimpleTokenDetails fragment to be used in all queries (#7549) 678e624 +* **web:** switch currency when input equals output (staging) 20277d2 +* **web:** TokenBalanceProvider account change (#7432) 516b781 +* **web:** use accent warning soft for outage banner icon wrapper (#7428) cda7ea9 +* **web:** use cache-first policy for TopTokens query (#7484) 25d0950 + + +### Code Refactoring + +* **web:** isolate the subscription updater (#7387) 6caabbd +* **web:** split out parseRemote signature (#7388) 223e766 +* **web:** use a single codepath for functions response transform (#7363) 60471a8 diff --git a/VERSION b/VERSION index e677453dc59..0f955cc1d70 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -web/5.23.5 \ No newline at end of file +web/5.24.0 \ No newline at end of file diff --git a/apps/mobile/README.md b/apps/mobile/README.md index 0bd76016b7f..ffab6f26402 100644 --- a/apps/mobile/README.md +++ b/apps/mobile/README.md @@ -111,6 +111,7 @@ Add the following to your .rc file Install [Android Studio](https://developer.android.com/studio) Add the following to your .rc file + ``` export ANDROID_HOME=$HOME/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/emulator @@ -175,7 +176,7 @@ These are some tools you might want to familiarize yourself with to understand t ## Migrations -We use `redux-persist` to persist Redux state between user sessions. When the Redux state schema is altered, a migration may be needed to transfer the existing persisted state to the new Redux schema. Failing to define a migration results in the app defaulting to the persisted schema, which will very likely cause `undefined` errors because the code has references to Redux state properties that were dropped in favor the the persisted schema. +We use `redux-persist` to persist Redux state between user sessions. When the Redux state schema is altered, a migration may be needed to transfer the existing persisted state to the new Redux schema. Failing to define a migration results in the app defaulting to the persisted schema, which will very likely cause `undefined` errors because the code has references to Redux state properties that were dropped in favor the persisted schema. ### When to define a migration diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 6f3253a345e..6c5294dd9b6 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -1151,12 +1151,8 @@ PODS: - React-Core - react-native-restart (0.0.27): - React-Core - - react-native-safe-area-context (4.5.0): - - RCT-Folly - - RCTRequired - - RCTTypeSafety + - react-native-safe-area-context (4.9.0): - React-Core - - ReactCommon/turbomodule/core - react-native-skia (0.1.187): - React - React-callinvoker @@ -1749,7 +1745,7 @@ SPEC CHECKSUMS: react-native-onesignal: ab800900cffeca4d9db70a05244013fc8a36ceb8 react-native-pager-view: 3051346698a0ba0c4e13e40097cc11b00ee03cca react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 - react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc + react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b react-native-skia: e7385e2f5ebe284df53f0def573198fe69a7bd72 react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581 diff --git a/apps/mobile/jest.config.js b/apps/mobile/jest.config.js index a583b497650..285f047fb4a 100644 --- a/apps/mobile/jest.config.js +++ b/apps/mobile/jest.config.js @@ -4,10 +4,6 @@ const preset = require('../../config/jest-presets/jest/jest-preset') module.exports = { ...preset, preset: 'jest-expo', - transform: { - ...preset.transform, - '^.+\\.jsx?$': 'babel-jest', - }, displayName: 'Mobile Wallet', collectCoverageFrom: [ 'src/**/*.{js,ts,tsx}', diff --git a/apps/mobile/package.json b/apps/mobile/package.json index bbbe4cb5349..fb605c4be1a 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -171,7 +171,6 @@ "@uniswap/eslint-config": "workspace:^", "@walletconnect/types": "2.11.2", "@welldone-software/why-did-you-render": "7.0.1", - "babel-jest": "29.6.1", "babel-loader": "8.2.3", "babel-plugin-react-native-web": "0.17.5", "babel-plugin-react-require": "4.0.0", diff --git a/apps/mobile/src/app/App.tsx b/apps/mobile/src/app/App.tsx index d8684a052e0..113aa3f2613 100644 --- a/apps/mobile/src/app/App.tsx +++ b/apps/mobile/src/app/App.tsx @@ -45,15 +45,14 @@ import { getSentryTracesSamplingRate, getStatsigEnvironmentTier, } from 'src/utils/version' -import { Statsig, StatsigProvider } from 'statsig-react-native' +import { StatsigProvider } from 'statsig-react-native' import { flexStyles, useIsDarkMode } from 'ui/src' import { config } from 'uniswap/src/config' import { uniswapUrls } from 'uniswap/src/constants/urls' -import { - DUMMY_STATSIG_SDK_KEY, - ExperimentsWallet, -} from 'uniswap/src/features/experiments/constants' -import { WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/experiments/flags' +import { DUMMY_STATSIG_SDK_KEY } from 'uniswap/src/features/statsig/constants' +import { WALLET_EXPERIMENTS } from 'uniswap/src/features/statsig/experiments' +import { WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/statsig/flags' +import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import i18n from 'uniswap/src/i18n/i18n' import { CurrencyId } from 'uniswap/src/types/currency' @@ -175,12 +174,13 @@ function SentryTags({ children }: PropsWithChildren): JSX.Element { Sentry.setTag(`featureFlag.${flagKey}`, Statsig.checkGateWithExposureLoggingDisabled(flagKey)) } - Object.entries(ExperimentsWallet).map(([_, experimentName]) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const [_, experimentDef] of WALLET_EXPERIMENTS.entries()) { Sentry.setTag( - `experiment.${experimentName}`, - Statsig.getExperimentWithExposureLoggingDisabled(experimentName).getGroupName() + `experiment.${experimentDef.name}`, + Statsig.getExperimentWithExposureLoggingDisabled(experimentDef.name).getGroupName() ) - }) + } }, []) return <>{children} diff --git a/apps/mobile/src/app/MobileWalletNavigationProvider.tsx b/apps/mobile/src/app/MobileWalletNavigationProvider.tsx index a6b09b6ce1c..c945cf54e5f 100644 --- a/apps/mobile/src/app/MobileWalletNavigationProvider.tsx +++ b/apps/mobile/src/app/MobileWalletNavigationProvider.tsx @@ -6,25 +6,21 @@ import { useAppStackNavigation } from 'src/app/navigation/types' import { closeModal, openModal } from 'src/features/modals/modalSlice' import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex' import { Screens } from 'src/screens/Screens' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { logger } from 'utilities/src/logger/logger' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { NavigateToNftItemArgs, - NavigateToSendArgs, + NavigateToSendFlowArgs, NavigateToSwapFlowArgs, ShareNftArgs, ShareTokenArgs, WalletNavigationProvider, + getNavigateToSendFlowArgsInitialState, getNavigateToSwapFlowArgsInitialState, } from 'wallet/src/contexts/WalletNavigationContext' -import { AssetType } from 'wallet/src/entities/assets' import { useFiatOnRampIpAddressQuery } from 'wallet/src/features/fiatOnRamp/api' -import { - CurrencyField, - TransactionState, -} from 'wallet/src/features/transactions/transactionState/types' import { sendWalletAnalyticsEvent } from 'wallet/src/telemetry' import { ModalName, ShareableEntity, WalletEventName } from 'wallet/src/telemetry/constants' import { getNftUrl, getTokenUrl } from 'wallet/src/utils/linking' @@ -122,24 +118,12 @@ function useNavigateToReceive(): () => void { }, [dispatch]) } -function useNavigateToSend(): (args: NavigateToSendArgs) => void { +function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void { const dispatch = useAppDispatch() return useCallback( - (args: NavigateToSendArgs) => { - const initialSendState: TransactionState = { - exactCurrencyField: CurrencyField.INPUT, - exactAmountToken: '', - [CurrencyField.INPUT]: args - ? { - address: args.currencyAddress, - chainId: args.chainId, - type: AssetType.Currency, - } - : null, - [CurrencyField.OUTPUT]: null, - showRecipientSelector: true, - } + (args: NavigateToSendFlowArgs) => { + const initialSendState = getNavigateToSendFlowArgsInitialState(args) dispatch(openModal({ name: ModalName.Send, initialState: initialSendState })) }, [dispatch] @@ -160,9 +144,18 @@ function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void { } function useNavigateToTokenDetails(): (currencyId: string) => void { - return useCallback((currencyId: string): void => { - exploreNavigationRef.navigate(Screens.TokenDetails, { currencyId }) - }, []) + const appNavigation = useAppStackNavigation() + + return useCallback( + (currencyId: string): void => { + if (exploreNavigationRef.isFocused()) { + exploreNavigationRef.navigate(Screens.TokenDetails, { currencyId }) + } else { + appNavigation.navigate(Screens.TokenDetails, { currencyId }) + } + }, + [appNavigation] + ) } function useNavigateToNftDetails(): (args: NavigateToNftItemArgs) => void { diff --git a/apps/mobile/src/app/migrations.test.ts b/apps/mobile/src/app/migrations.test.ts index 2df635ba087..c62de8fb30b 100644 --- a/apps/mobile/src/app/migrations.test.ts +++ b/apps/mobile/src/app/migrations.test.ts @@ -62,6 +62,7 @@ import { v5Schema, v60Schema, v61Schema, + v62Schema, v6Schema, v7Schema, v8Schema, @@ -1390,4 +1391,11 @@ describe('Redux state migrations', () => { expect(v62.behaviorHistory.extensionOnboardingState).toBe(ExtensionOnboardingState.Undefined) }) + + it('migrates from v62 to 63', () => { + const v62Stub = { ...v62Schema } + const v63 = migrations[63](v62Stub) + + expect(v63.wallet.isUnlocked).toBe(undefined) + }) }) diff --git a/apps/mobile/src/app/migrations.ts b/apps/mobile/src/app/migrations.ts index 266352da374..f1041c618f4 100644 --- a/apps/mobile/src/app/migrations.ts +++ b/apps/mobile/src/app/migrations.ts @@ -875,4 +875,11 @@ export const migrations = { return newState }, + + 63: function removeWalletIsUnlockedState(state: any) { + const newState = { ...state } + delete newState.wallet.isUnlocked + + return newState + }, } diff --git a/apps/mobile/src/app/modals/AccountSwitcherModal.tsx b/apps/mobile/src/app/modals/AccountSwitcherModal.tsx index c378c139fd7..7d953c050ce 100644 --- a/apps/mobile/src/app/modals/AccountSwitcherModal.tsx +++ b/apps/mobile/src/app/modals/AccountSwitcherModal.tsx @@ -22,8 +22,8 @@ import { useSporeColors, } from 'ui/src' import { spacing } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { isAndroid } from 'uniswap/src/utils/platform' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { ActionSheetModal, MenuItemProp } from 'wallet/src/components/modals/ActionSheetModal' diff --git a/apps/mobile/src/app/modals/ExperimentsModal.tsx b/apps/mobile/src/app/modals/ExperimentsModal.tsx index 36acfbf902c..97d689a7276 100644 --- a/apps/mobile/src/app/modals/ExperimentsModal.tsx +++ b/apps/mobile/src/app/modals/ExperimentsModal.tsx @@ -6,11 +6,6 @@ import { useAppDispatch, useAppSelector } from 'src/app/hooks' import { closeModal } from 'src/features/modals/modalSlice' import { selectCustomEndpoint } from 'src/features/tweaks/selectors' import { setCustomEndpoint } from 'src/features/tweaks/slice' -import { - ConfigResult, - Statsig, - useExperimentWithExposureLoggingDisabled, -} from 'statsig-react-native' import { Accordion, Button, @@ -23,15 +18,20 @@ import { } from 'ui/src' import { spacing } from 'ui/src/theme' import { - EXPERIMENT_VALUES_BY_EXPERIMENT, - ExperimentsWallet, -} from 'uniswap/src/features/experiments/constants' + Experiments, + WALLET_EXPERIMENTS, + getExperimentDefinition, +} from 'uniswap/src/features/statsig/experiments' import { FeatureFlags, WALLET_FEATURE_FLAG_NAMES, getFeatureFlagName, -} from 'uniswap/src/features/experiments/flags' -import { useFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/experiments/hooks' +} from 'uniswap/src/features/statsig/flags' +import { + useExperimentValueWithExposureLoggingDisabled, + useFeatureFlagWithExposureLoggingDisabled, +} from 'uniswap/src/features/statsig/hooks' +import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig' import { Switch } from 'wallet/src/components/buttons/Switch' import { TextInput } from 'wallet/src/components/input/TextInput' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' @@ -65,11 +65,16 @@ export function ExperimentsModal(): JSX.Element { } } - const featureFlagRows = [] + const featureFlagRows: JSX.Element[] = [] for (const [flag, flagName] of WALLET_FEATURE_FLAG_NAMES.entries()) { featureFlagRows.push() } + const experimentRows: JSX.Element[] = [] + for (const [experiment, experimentDef] of WALLET_EXPERIMENTS.entries()) { + experimentRows.push() + } + return ( - {Object.values(ExperimentsWallet).map((experiment) => { - return - })} + {experimentRows} @@ -196,75 +199,52 @@ function FeatureFlagRow({ flag }: { flag: FeatureFlags }): JSX.Element { ) } -function ExperimentRow({ name }: { name: string }): JSX.Element { - const experiment = useExperimentWithExposureLoggingDisabled(name) - - const params = Object.entries(experiment.config.value).map(([key, value]) => ( - - {key} - - - )) +function ExperimentRow({ experiment }: { experiment: Experiments }): JSX.Element { + const experimentDef = getExperimentDefinition(experiment) return ( <> - {name} - {params} + {experimentDef.name} + + + + + + ) } -function ExperimentValueSwitch({ - experiment, - configValueContent, - configValueName, -}: { - experiment: ConfigResult - configValueContent: unknown - configValueName: string -}): JSX.Element { +function ExperimentValueSwitch({ experiment }: { experiment: Experiments }): JSX.Element { const colors = useSporeColors() - const experimentName = experiment.config.getName() - - const onValueChange = (newValue: boolean | string): void => { - Statsig.overrideConfig(experimentName, { - ...experiment.config.value, - [configValueName]: newValue, - }) - } + const experimentDef = getExperimentDefinition(experiment) + const currentValue = useExperimentValueWithExposureLoggingDisabled(experiment) - if (typeof configValueContent === 'boolean') { - return - } - - const variants = EXPERIMENT_VALUES_BY_EXPERIMENT[experimentName]?.[configValueName] - - if (variants && typeof configValueContent === 'string') { - return ( - - {Object.entries(variants).map(([_, value]) => ( - onValueChange(value)}> - - {value} - - - ))} - - ) - } - - return Unknown Variants + return ( + + {experimentDef.values.map((value) => ( + { + Statsig.overrideConfig(experimentDef.name, { + [experimentDef.key]: value, + }) + }}> + + {value} + + + ))} + + ) } diff --git a/apps/mobile/src/app/modals/TransferTokenModal.tsx b/apps/mobile/src/app/modals/TransferTokenModal.tsx index 005feb4d92d..2123fc42055 100644 --- a/apps/mobile/src/app/modals/TransferTokenModal.tsx +++ b/apps/mobile/src/app/modals/TransferTokenModal.tsx @@ -5,8 +5,8 @@ import { selectModalState } from 'src/features/modals/selectModalState' import { TransferFlow } from 'src/features/transactions/transfer/TransferFlow' import { TransferFlow as TransferFlowRewrite } from 'src/features/transactions/transfer/transferRewrite/TransferFlow' import { useSporeColors } from 'ui/src' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { ModalName } from 'wallet/src/telemetry/constants' diff --git a/apps/mobile/src/app/navigation/hooks.ts b/apps/mobile/src/app/navigation/hooks.ts index ea5cb48c2c5..22d735e6ae8 100644 --- a/apps/mobile/src/app/navigation/hooks.ts +++ b/apps/mobile/src/app/navigation/hooks.ts @@ -93,7 +93,7 @@ export function useEagerExternalProfileRootNavigation(): { /** * Utility hook that checks if the caller is part of the navigation tree. * - * Inspired by how the navigation library checks if the the navigation object exists. + * Inspired by how the navigation library checks if the navigation object exists. * https://github.com/react-navigation/react-navigation/blob/d7032ba8bb6ae24030a47f0724b61b561132fca6/packages/core/src/useNavigation.tsx#L18 */ export function useIsPartOfNavigationTree(): boolean { diff --git a/apps/mobile/src/app/navigation/navigation.tsx b/apps/mobile/src/app/navigation/navigation.tsx index d26e7e73c3e..4d5b8add8a2 100644 --- a/apps/mobile/src/app/navigation/navigation.tsx +++ b/apps/mobile/src/app/navigation/navigation.tsx @@ -64,8 +64,8 @@ import { TokenDetailsScreen } from 'src/screens/TokenDetailsScreen' import { WebViewScreen } from 'src/screens/WebViewScreen' import { Icons, useDeviceInsets, useSporeColors } from 'ui/src' import { spacing } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { OnboardingEntryPoint } from 'wallet/src/features/onboarding/types' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' import { selectFinishedOnboarding } from 'wallet/src/features/wallet/selectors' diff --git a/apps/mobile/src/app/schema.ts b/apps/mobile/src/app/schema.ts index 70bc54dc125..372581bed30 100644 --- a/apps/mobile/src/app/schema.ts +++ b/apps/mobile/src/app/schema.ts @@ -475,6 +475,19 @@ export const v62Schema = { }, } +const v63SchemaIntermediate = { + ...v62Schema, + wallet: { + ...v62Schema.wallet, + isUnlocked: undefined, + }, +} + +// We will no longer keep track of this in the redux state. +delete v63SchemaIntermediate.wallet.isUnlocked + +export const v63Schema = v63SchemaIntermediate + // TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer // export const getSchema = (): RootState => v0Schema -export const getSchema = (): typeof v62Schema => v62Schema +export const getSchema = (): typeof v63Schema => v63Schema diff --git a/apps/mobile/src/app/store.ts b/apps/mobile/src/app/store.ts index 537c0208fe8..60701c7a9ff 100644 --- a/apps/mobile/src/app/store.ts +++ b/apps/mobile/src/app/store.ts @@ -75,7 +75,7 @@ export const persistConfig = { key: 'root', storage: reduxStorage, whitelist, - version: 62, + version: 63, migrate: createMigrate(migrations), } diff --git a/apps/mobile/src/components/QRCodeScanner/QRCodeScanner.tsx b/apps/mobile/src/components/QRCodeScanner/QRCodeScanner.tsx index 9c8e32dd696..80f2d3d056d 100644 --- a/apps/mobile/src/components/QRCodeScanner/QRCodeScanner.tsx +++ b/apps/mobile/src/components/QRCodeScanner/QRCodeScanner.tsx @@ -128,7 +128,9 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E }, [permissionStatus, requestPermissionResponse, t]) const overlayWidth = (overlayLayout?.height ?? 0) / CAMERA_ASPECT_RATIO - const scannerSize = Math.min(overlayWidth, dimensions.fullWidth) * SCAN_ICON_WIDTH_RATIO + const cameraWidth = dimensions.fullWidth + const cameraHeight = CAMERA_ASPECT_RATIO * cameraWidth + const scannerSize = Math.min(overlayWidth, cameraWidth) * SCAN_ICON_WIDTH_RATIO return ( - + {permissionStatus === PermissionStatus.GRANTED && !isReadingImageFile && ( void onConfirm: () => void + disableConfirm?: boolean } > @@ -46,6 +47,7 @@ export function ModalWithOverlay({ scrollDownButtonText, onReject, onConfirm, + disableConfirm, ...bottomSheetModalProps }: ModalWithOverlayProps): JSX.Element { const scrollViewRef = useRef(null) @@ -124,7 +126,7 @@ export function ModalWithOverlay({ { + const getReadableMethodName = ( + ethMethod: EthMethod | UwULinkMethod, + dappNameOrUrl: string + ): JSX.Element => { switch (ethMethod) { case EthMethod.PersonalSign: case EthMethod.EthSign: case EthMethod.SignTypedData: return case EthMethod.EthSendTransaction: + case UwULinkMethod.Erc20Send: return } diff --git a/apps/mobile/src/components/WalletConnect/RequestModal/UwULinkErc20SendModal.tsx b/apps/mobile/src/components/WalletConnect/RequestModal/UwULinkErc20SendModal.tsx new file mode 100644 index 00000000000..3b82d743ead --- /dev/null +++ b/apps/mobile/src/components/WalletConnect/RequestModal/UwULinkErc20SendModal.tsx @@ -0,0 +1,137 @@ +import { useBottomSheetInternal } from '@gorhom/bottom-sheet' +import { formatUnits } from 'ethers/lib/utils' +import { useTranslation } from 'react-i18next' +import Animated, { useAnimatedStyle } from 'react-native-reanimated' +import { ModalWithOverlay } from 'src/components/WalletConnect/ModalWithOverlay/ModalWithOverlay' +import { UwuLinkErc20Request } from 'src/features/walletConnect/walletConnectSlice' +import { Flex, Text } from 'ui/src' +import { iconSizes } from 'ui/src/theme' +import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' +import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo' +import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader' +import { CHAIN_INFO } from 'wallet/src/constants/chains' +import { useOnChainCurrencyBalance } from 'wallet/src/features/portfolio/api' +import { NativeCurrency } from 'wallet/src/features/tokens/NativeCurrency' +import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo' +import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' +import { ModalName } from 'wallet/src/telemetry/constants' +import { buildCurrencyId } from 'wallet/src/utils/currencyId' + +type Props = { + onClose: () => void + onConfirm: () => void + onReject: () => void + request: UwuLinkErc20Request + hasSufficientGasFunds: boolean +} + +export function UwULinkErc20SendModal({ + onClose, + onConfirm, + onReject, + request, + hasSufficientGasFunds, +}: Props): JSX.Element { + const { t } = useTranslation() + const activeAccountAddress = useActiveAccountAddressWithThrow() + // TODO: wallet should determine if the currency is stablecoin + const { chainId, tokenAddress, amount } = request + const currencyInfo = useCurrencyInfo(buildCurrencyId(chainId, tokenAddress)) + const { balance } = useOnChainCurrencyBalance(currencyInfo?.currency, activeAccountAddress) + + const hasSufficientTokenFunds = !balance?.lessThan(amount) + + return ( + + + + ) +} + +function UwULinkErc20SendModalContent({ + request, + loading, + currencyInfo, + hasSufficientGasFunds, + hasSufficientTokenFunds, +}: { + request: UwuLinkErc20Request + loading: boolean + hasSufficientGasFunds: boolean + hasSufficientTokenFunds: boolean + currencyInfo: Maybe +}): JSX.Element { + const { t } = useTranslation() + const { animatedFooterHeight } = useBottomSheetInternal() + const bottomSpacerStyle = useAnimatedStyle(() => ({ + height: animatedFooterHeight.value, + })) + + const { chainId, isStablecoin } = request + const nativeCurrency = chainId && NativeCurrency.onChain(chainId) + + if (loading || !currencyInfo) { + return ( + + + + + ) + } + + const { + logoUrl, + currency: { name, symbol, decimals }, + } = currencyInfo + + return ( + + {request.recipient.name} + + {!hasSufficientTokenFunds && ( + + {t('uwulink.error.insufficientTokens', { + tokenSymbol: symbol, + chain: CHAIN_INFO[chainId].label, + })} + + )} + {`${isStablecoin ? '$' : ''}${formatUnits( + request.amount, + decimals + )}`} + + + {symbol} + + + {!hasSufficientGasFunds && ( + + {t('walletConnect.request.error.insufficientFunds', { + currencySymbol: nativeCurrency?.symbol, + })} + + )} + + + ) +} diff --git a/apps/mobile/src/components/WalletConnect/RequestModal/WalletConnectRequestModal.tsx b/apps/mobile/src/components/WalletConnect/RequestModal/WalletConnectRequestModal.tsx index e02a892dd53..06eba03c531 100644 --- a/apps/mobile/src/components/WalletConnect/RequestModal/WalletConnectRequestModal.tsx +++ b/apps/mobile/src/components/WalletConnect/RequestModal/WalletConnectRequestModal.tsx @@ -18,21 +18,26 @@ import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { selectDidOpenFromDeepLink } from 'src/features/walletConnect/selectors' import { signWcRequestActions } from 'src/features/walletConnect/signWcRequestSaga' import { - SignRequest, - TransactionRequest, + WalletConnectRequest, isTransactionRequest, } from 'src/features/walletConnect/walletConnectSlice' import { useTransactionGasFee } from 'wallet/src/features/gas/hooks' import { GasSpeed } from 'wallet/src/features/gas/types' import { useIsBlocked, useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks' import { useSignerAccounts } from 'wallet/src/features/wallet/hooks' -import { EthMethod, WCEventType, WCRequestOutcome } from 'wallet/src/features/walletConnect/types' +import { + EthMethod, + UwULinkMethod, + WCEventType, + WCRequestOutcome, +} from 'wallet/src/features/walletConnect/types' import { ModalName } from 'wallet/src/telemetry/constants' import { areAddressesEqual } from 'wallet/src/utils/addresses' +import { UwULinkErc20SendModal } from './UwULinkErc20SendModal' interface Props { onClose: () => void - request: SignRequest | TransactionRequest + request: WalletConnectRequest } const VALID_REQUEST_TYPES = [ @@ -41,6 +46,7 @@ const VALID_REQUEST_TYPES = [ EthMethod.SignTypedDataV4, EthMethod.EthSign, EthMethod.EthSendTransaction, + UwULinkMethod.Erc20Send, ] export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Element | null { @@ -62,6 +68,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem areAddressesEqual(account.address, request.account) ) const gasFee = useTransactionGasFee(tx, GasSpeed.Urgent) + const hasSufficientFunds = useHasSufficientFunds({ account: request.account, chainId, @@ -145,7 +152,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem if (!confirmEnabled || !signerAccount) { return } - if (request.type === EthMethod.EthSendTransaction) { + if (request.type === EthMethod.EthSendTransaction || request.type === UwULinkMethod.Erc20Send) { if (!gasFee.params) { return } // appeasing typescript @@ -153,7 +160,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem signWcRequestActions.trigger({ sessionId: request.sessionId, requestInternalId: request.internalId, - method: request.type, + method: EthMethod.EthSendTransaction, transaction: { ...tx, ...gasFee.params }, account: signerAccount, dapp: request.dapp, @@ -198,6 +205,14 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem const { trigger: actionButtonTrigger } = useBiometricPrompt(onConfirm) const { requiredForTransactions } = useBiometricAppSettings() + const onConfirmPress = async (): Promise => { + if (requiredForTransactions) { + await actionButtonTrigger() + } else { + await onConfirm() + } + } + if (!VALID_REQUEST_TYPES.includes(request.type)) { return null } @@ -210,6 +225,18 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem } } + if (request.type === UwULinkMethod.Erc20Send) { + return ( + + ) + } + return ( => { - if (requiredForTransactions) { - await actionButtonTrigger() - } else { - await onConfirm() - } - }} + onConfirm={onConfirmPress} onReject={onReject}> return { currencyId, amount } } catch (error) { - logger.error(error, { tags: { file: 'WalletConnectRequestModal', function: 'getPermitInfo' } }) + logger.error(error, { + tags: { file: 'WalletConnectRequestModal', function: 'getPermitInfo' }, + }) return undefined } } @@ -60,7 +63,7 @@ const getPermitInfo = (request: WalletConnectRequest): PermitInfo | undefined => type WalletConnectRequestModalContentProps = { gasFee: GasFeeResult hasSufficientFunds: boolean - request: WalletConnectRequest + request: SignRequest | TransactionRequest isBlocked: boolean } diff --git a/apps/mobile/src/components/WalletConnect/ScanSheet/SwitchAccountOption.tsx b/apps/mobile/src/components/WalletConnect/ScanSheet/SwitchAccountOption.tsx index d7f74317db1..a88fec60214 100644 --- a/apps/mobile/src/components/WalletConnect/ScanSheet/SwitchAccountOption.tsx +++ b/apps/mobile/src/components/WalletConnect/ScanSheet/SwitchAccountOption.tsx @@ -1,8 +1,8 @@ import React from 'react' import { Flex, Separator, Text, Unicon, UniconV2, useSporeColors } from 'ui/src' import Check from 'ui/src/assets/icons/check.svg' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { DisplayNameText } from 'wallet/src/components/accounts/DisplayNameText' import { Account } from 'wallet/src/features/wallet/accounts/types' import { useDisplayName } from 'wallet/src/features/wallet/hooks' diff --git a/apps/mobile/src/components/WalletConnect/ScanSheet/WalletConnectModal.tsx b/apps/mobile/src/components/WalletConnect/ScanSheet/WalletConnectModal.tsx index e5c09c6401e..90eefdf48ea 100644 --- a/apps/mobile/src/components/WalletConnect/ScanSheet/WalletConnectModal.tsx +++ b/apps/mobile/src/components/WalletConnect/ScanSheet/WalletConnectModal.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Alert } from 'react-native' import 'react-native-reanimated' -import { useAppDispatch, useAppSelector } from 'src/app/hooks' +import { useAppDispatch } from 'src/app/hooks' import { useEagerExternalProfileRootNavigation } from 'src/app/navigation/hooks' import { QRCodeScanner } from 'src/components/QRCodeScanner/QRCodeScanner' import Trace from 'src/components/Trace/Trace' @@ -10,8 +10,10 @@ import { ConnectedDappsList } from 'src/components/WalletConnect/ConnectedDapps/ import { URIType, UWULINK_PREFIX, + findAllowedTokenRecipient, getSupportedURI, isAllowedUwuLinkRequest, + toTokenTransferRequest, useUwuLinkContractAllowlist, } from 'src/components/WalletConnect/ScanSheet/util' import { BackButtonView } from 'src/components/layout/BackButtonView' @@ -23,14 +25,15 @@ import { Flex, HapticFeedback, Text, TouchableArea, useIsDarkMode, useSporeColor import Scan from 'ui/src/assets/icons/receive.svg' import ScanQRIcon from 'ui/src/assets/icons/scan.svg' import { iconSizes } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { logger } from 'utilities/src/logger/logger' import { WalletQRCode } from 'wallet/src/components/QRCodeScanner/WalletQRCode' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' -import { selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors' -import { EthMethod, UwULinkRequest } from 'wallet/src/features/walletConnect/types' +import { useContractManager, useProviderManager } from 'wallet/src/features/wallet/context' +import { useActiveAccount } from 'wallet/src/features/wallet/hooks' +import { EthMethod, UwULinkMethod, UwULinkRequest } from 'wallet/src/features/walletConnect/types' import { ElementName, ModalName } from 'wallet/src/telemetry/constants' type Props = { @@ -45,8 +48,8 @@ export function WalletConnectModal({ const { t } = useTranslation() const colors = useSporeColors() const isDarkMode = useIsDarkMode() - const activeAddress = useAppSelector(selectActiveAccountAddress) - const { sessions, hasPendingSessionError } = useWalletConnect(activeAddress) + const activeAccount = useActiveAccount() + const { sessions, hasPendingSessionError } = useWalletConnect(activeAccount?.address) const [currentScreenState, setCurrentScreenState] = useState(initialScreenState) const [shouldFreezeCamera, setShouldFreezeCamera] = useState(false) @@ -57,6 +60,9 @@ export function WalletConnectModal({ const uwuLinkContractAllowlist = useUwuLinkContractAllowlist() + const providerManager = useProviderManager() + const contractManager = useContractManager() + // Update QR scanner states when pending session error alert is shown from WCv2 saga event channel useEffect(() => { if (hasPendingSessionError) { @@ -68,7 +74,7 @@ export function WalletConnectModal({ const onScanCode = useCallback( async (uri: string) => { // don't scan any QR codes if there is an error popup open or camera is frozen - if (!activeAddress || hasPendingSessionError || shouldFreezeCamera) { + if (!activeAccount || hasPendingSessionError || shouldFreezeCamera) { return } await HapticFeedback.selection() @@ -149,7 +155,6 @@ export function WalletConnectModal({ try { const parsedUwulinkRequest: UwULinkRequest = JSON.parse(supportedURI.value) const isAllowed = isAllowedUwuLinkRequest(parsedUwulinkRequest, uwuLinkContractAllowlist) - if (!isAllowed) { Alert.alert( t('walletConnect.error.uwu.title'), @@ -166,25 +171,62 @@ export function WalletConnectModal({ return } - dispatch( - addRequest({ - account: activeAddress, - request: { - type: EthMethod.EthSendTransaction, - transaction: { from: activeAddress, ...parsedUwulinkRequest.value }, - sessionId: UWULINK_PREFIX, // session/internalId is WalletConnect specific, but not needed here - internalId: UWULINK_PREFIX, - account: activeAddress, - dapp: { - ...parsedUwulinkRequest.dapp, - source: UWULINK_PREFIX, - chain_id: parsedUwulinkRequest.chainId, - webhook: parsedUwulinkRequest.webhook, + const newRequest = { + sessionId: UWULINK_PREFIX, // session/internalId is WalletConnect specific, but not needed here + internalId: UWULINK_PREFIX, + account: activeAccount?.address, + dapp: { + name: '', + url: '', + ...parsedUwulinkRequest.dapp, + source: UWULINK_PREFIX, + chain_id: parsedUwulinkRequest.chainId, + webhook: parsedUwulinkRequest.webhook, + }, + chainId: parsedUwulinkRequest.chainId, + } + + if (parsedUwulinkRequest.method === UwULinkMethod.Erc20Send) { + const preparedTransaction = await toTokenTransferRequest( + parsedUwulinkRequest, + activeAccount, + providerManager, + contractManager + ) + const tokenRecipient = findAllowedTokenRecipient( + parsedUwulinkRequest, + uwuLinkContractAllowlist + ) + + dispatch( + addRequest({ + account: activeAccount.address, + request: { + ...newRequest, + type: UwULinkMethod.Erc20Send, + recipient: { + address: parsedUwulinkRequest.recipient, + name: tokenRecipient?.name ?? '', + }, + amount: parsedUwulinkRequest.amount, + tokenAddress: parsedUwulinkRequest.tokenAddress, + isStablecoin: parsedUwulinkRequest.isStablecoin, + transaction: { from: activeAccount.address, ...preparedTransaction }, }, - chainId: parsedUwulinkRequest.chainId, - }, - }) - ) + }) + ) + } else { + dispatch( + addRequest({ + account: activeAccount.address, + request: { + ...newRequest, + type: EthMethod.EthSendTransaction, + transaction: { from: activeAccount.address, ...parsedUwulinkRequest.value }, + }, + }) + ) + } onClose() } catch (_) { setShouldFreezeCamera(false) @@ -206,7 +248,7 @@ export function WalletConnectModal({ } }, [ - activeAddress, + activeAccount, hasPendingSessionError, shouldFreezeCamera, isUwULinkEnabled, @@ -217,6 +259,8 @@ export function WalletConnectModal({ onClose, dispatch, uwuLinkContractAllowlist, + providerManager, + contractManager, ] ) @@ -236,7 +280,7 @@ export function WalletConnectModal({ setCurrentScreenState(ScannerModalState.ScanQr) } - if (!activeAddress) { + if (!activeAccount) { return null } @@ -269,7 +313,7 @@ export function WalletConnectModal({ )} {currentScreenState === ScannerModalState.WalletQr && ( - + )} diff --git a/apps/mobile/src/components/WalletConnect/ScanSheet/util.ts b/apps/mobile/src/components/WalletConnect/ScanSheet/util.ts index 40e0bdc703e..c47700d0975 100644 --- a/apps/mobile/src/components/WalletConnect/ScanSheet/util.ts +++ b/apps/mobile/src/components/WalletConnect/ScanSheet/util.ts @@ -6,12 +6,24 @@ import { UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM, UNISWAP_WALLETCONNECT_URL, } from 'src/features/deepLinking/constants' -import { DynamicConfigs } from 'uniswap/src/features/experiments/configs' -import { useDynamicConfig } from 'uniswap/src/features/experiments/hooks' +import { DynamicConfigs } from 'uniswap/src/features/statsig/configs' +import { useDynamicConfig } from 'uniswap/src/features/statsig/hooks' import { logger } from 'utilities/src/logger/logger' +import { RPCType } from 'wallet/src/constants/chains' +import { AssetType } from 'wallet/src/entities/assets' +import { ContractManager } from 'wallet/src/features/contracts/ContractManager' +import { ProviderManager } from 'wallet/src/features/providers' import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types' -import { UwULinkRequest } from 'wallet/src/features/walletConnect/types' -import { getValidAddress } from 'wallet/src/utils/addresses' +import { getTokenTransferRequest } from 'wallet/src/features/transactions/transfer/hooks/useTransferTransactionRequest' +import { TransferCurrencyParams } from 'wallet/src/features/transactions/transfer/types' +import { Account } from 'wallet/src/features/wallet/accounts/types' +import { + EthTransaction, + UwULinkErc20SendRequest, + UwULinkMethod, + UwULinkRequest, +} from 'wallet/src/features/walletConnect/types' +import { areAddressesEqual, getValidAddress } from 'wallet/src/utils/addresses' export enum URIType { WalletConnectURL = 'walletconnect', @@ -34,19 +46,23 @@ interface EnabledFeatureFlags { // This type must match the format in statsig dynamic config for uwulink // https://console.statsig.com/5HjUux4OvSGzgqWIfKFt8i/dynamic_configs/uwulink_config -type UwuLinkAllowlistItem = { +type UwULinkAllowlistItem = { chainId: number - contractAddress: string + address: string name: string icon?: string } -type UwuLinkAllowlist = UwuLinkAllowlistItem[] + +type UwULinkAllowlist = { + contracts: UwULinkAllowlistItem[] + tokenRecipients: UwULinkAllowlistItem[] +} const UWULINK_MAX_TXN_VALUE = '0.001' const EASTER_EGG_QR_CODE = 'DO_NOT_SCAN_OR_ELSE_YOU_WILL_GO_TO_MOBILE_TEAM_JAIL' export const CUSTOM_UNI_QR_CODE_PREFIX = 'hello_uniwallet:' -export const UWULINK_PREFIX = 'uwulink' +export const UWULINK_PREFIX = 'uwulink' as const export const truncateQueryParams = (url: string): string => { // In fact, the first element will be always returned below. url is @@ -102,8 +118,9 @@ export async function getSupportedURI( return { type: URIType.EasterEgg, value: uri } } - if (enabledFeatureFlags?.isUwULinkEnabled && isUwULink(uri)) { - return { type: URIType.UwULink, value: uri.slice(UWULINK_PREFIX.length) } + if (isUwULink(uri)) { + // remove escape strings from the stringified JSON before parsing it + return { type: URIType.UwULink, value: uri.slice(UWULINK_PREFIX.length).replaceAll('\\', '') } } } @@ -132,11 +149,24 @@ function isUwULink(uri: string): boolean { // Gets the UWULink contract allow list from statsig dynamic config. // We can safely cast as long as the statsig config format matches our `UwuLinkAllowlist` type. -export function useUwuLinkContractAllowlist(): UwuLinkAllowlist { +export function useUwuLinkContractAllowlist(): UwULinkAllowlist { const uwuLinkConfig = useDynamicConfig(DynamicConfigs.UwuLink) - return uwuLinkConfig.getValue('allowlist') as UwuLinkAllowlist + return uwuLinkConfig.getValue('allowlist') as UwULinkAllowlist } +export function findAllowedTokenRecipient( + request: UwULinkRequest, + allowlist: UwULinkAllowlist +): UwULinkAllowlistItem | undefined { + if (request.method !== UwULinkMethod.Erc20Send) { + return + } + + const { chainId, recipient } = request + return allowlist.tokenRecipients.find( + (item) => item.chainId === chainId && areAddressesEqual(item.address, recipient) + ) +} /** * Util function to check if a UwULinkRequest is valid. * @@ -144,17 +174,26 @@ export function useUwuLinkContractAllowlist(): UwuLinkAllowlist { * 1. The to address is in the UWULINK_CONTRACT_ALLOWLIST * 2. The value is less than or equal to UWULINK_MAX_TXN_VALUE * + * TODO: also check for validity of the entire request object (e.g. all the required fields exist) + * * @param request parsed UwULinkRequest * @returns boolean for whether the UwULinkRequest is allowed */ export function isAllowedUwuLinkRequest( request: UwULinkRequest, - allowList: UwuLinkAllowlist + allowlist: UwULinkAllowlist ): boolean { + // token sends + if (request.method === UwULinkMethod.Erc20Send) { + return Boolean(findAllowedTokenRecipient(request, allowlist)) + } + + // generic transactions const { to, value } = request.value const belowMaximumValue = value && parseFloat(value) <= parseEther(UWULINK_MAX_TXN_VALUE).toNumber() - const isAllowedContractAddress = to && allowList.some((item) => item.contractAddress === to) + const isAllowedContractAddress = + to && allowlist.contracts.some((item) => areAddressesEqual(item.address, to)) if (!belowMaximumValue || !isAllowedContractAddress) { return false @@ -238,3 +277,22 @@ export function parseScantasticParams(uri: string): ScantasticParams | undefined }) } } + +export async function toTokenTransferRequest( + request: UwULinkErc20SendRequest, + account: Account, + providerManager: ProviderManager, + contractManager: ContractManager +): Promise { + const provider = providerManager.getProvider(request.chainId, RPCType.Public) + const params: TransferCurrencyParams = { + type: AssetType.Currency, + account, + chainId: request.chainId, + toAddress: request.recipient, + tokenAddress: request.tokenAddress, + amountInWei: request.amount.toString(), + } + const transaction = await getTokenTransferRequest(params, provider, contractManager) + return transaction as EthTransaction +} diff --git a/apps/mobile/src/components/explore/search/SearchEmptySection.tsx b/apps/mobile/src/components/explore/search/SearchEmptySection.tsx index 75fddeefb0d..f4bebdf7120 100644 --- a/apps/mobile/src/components/explore/search/SearchEmptySection.tsx +++ b/apps/mobile/src/components/explore/search/SearchEmptySection.tsx @@ -10,8 +10,8 @@ import { SectionHeaderText } from 'src/components/explore/search/SearchSectionHe import { AnimatedFlex, Flex, Icons, Text, TouchableArea, useSporeColors } from 'ui/src' import ClockIcon from 'ui/src/assets/icons/clock.svg' import { iconSizes } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { clearSearchHistory } from 'wallet/src/features/search/searchHistorySlice' import { SearchResult, diff --git a/apps/mobile/src/components/fiatOnRamp/CtaButton.tsx b/apps/mobile/src/components/fiatOnRamp/CtaButton.tsx index 192b1215bda..7a160a93e74 100644 --- a/apps/mobile/src/components/fiatOnRamp/CtaButton.tsx +++ b/apps/mobile/src/components/fiatOnRamp/CtaButton.tsx @@ -1,17 +1,13 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import Trace from 'src/components/Trace/Trace' -import { MobileEventName } from 'src/features/telemetry/constants' import { Button, Icons } from 'ui/src' import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader' -import { ElementName } from 'wallet/src/telemetry/constants' interface FiatOnRampCtaButtonProps { onPress: () => void isLoading?: boolean eligible: boolean disabled: boolean - analyticsProperties?: Record continueButtonText: string } @@ -20,35 +16,28 @@ export function FiatOnRampCtaButton({ isLoading, eligible, disabled, - analyticsProperties, onPress, }: FiatOnRampCtaButtonProps): JSX.Element { const { t } = useTranslation() const buttonAvailable = eligible || isLoading const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported') return ( - - - + ) } diff --git a/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx b/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx index cf2ea7bf6a0..74fb50a3974 100644 --- a/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx +++ b/apps/mobile/src/components/forceUpgrade/ForceUpgradeModal.tsx @@ -5,8 +5,8 @@ import { SeedPhraseDisplay } from 'src/components/mnemonic/SeedPhraseDisplay' import { APP_STORE_LINK } from 'src/constants/urls' import { UpgradeStatus } from 'src/features/forceUpgrade/types' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' -import { DynamicConfigs } from 'uniswap/src/features/experiments/configs' -import { useDynamicConfig } from 'uniswap/src/features/experiments/hooks' +import { DynamicConfigs } from 'uniswap/src/features/statsig/configs' +import { useDynamicConfig } from 'uniswap/src/features/statsig/hooks' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { WarningModal } from 'wallet/src/components/modals/WarningModal/WarningModal' import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types' diff --git a/apps/mobile/src/components/home/NftsTab.tsx b/apps/mobile/src/components/home/NftsTab.tsx index e13462b6ee8..d69606bdd7c 100644 --- a/apps/mobile/src/components/home/NftsTab.tsx +++ b/apps/mobile/src/components/home/NftsTab.tsx @@ -2,13 +2,13 @@ import { FlashList } from '@shopify/flash-list' import React, { forwardRef, memo, useCallback, useMemo } from 'react' import { RefreshControl } from 'react-native' import { useAppStackNavigation } from 'src/app/navigation/types' -import { NftView } from 'src/components/NFT/NftView' import { useAdaptiveFooter } from 'src/components/home/hooks' import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' import { Screens } from 'src/screens/Screens' import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' import { isAndroid } from 'uniswap/src/utils/platform' +import { NftView } from 'wallet/src/components/nfts/NftView' import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NFTItem } from 'wallet/src/features/nfts/types' diff --git a/apps/mobile/src/components/home/WalletEmptyState.tsx b/apps/mobile/src/components/home/WalletEmptyState.tsx index b2fae660b77..3ccdd668d64 100644 --- a/apps/mobile/src/components/home/WalletEmptyState.tsx +++ b/apps/mobile/src/components/home/WalletEmptyState.tsx @@ -6,8 +6,8 @@ import { openModal } from 'src/features/modals/modalSlice' import { Flex, Icons, Text, TouchableArea } from 'ui/src' import PaperStackIcon from 'ui/src/assets/icons/paper-stack.svg' import { iconSizes, colors as rawColors } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { AccountType } from 'wallet/src/features/wallet/accounts/types' import { useActiveAccount } from 'wallet/src/features/wallet/hooks' diff --git a/apps/mobile/src/components/unitags/ChooseNftModal.tsx b/apps/mobile/src/components/unitags/ChooseNftModal.tsx index 5fc616a3d27..ca940ab752c 100644 --- a/apps/mobile/src/components/unitags/ChooseNftModal.tsx +++ b/apps/mobile/src/components/unitags/ChooseNftModal.tsx @@ -1,7 +1,7 @@ -import { NftView } from 'src/components/NFT/NftView' import { useDeviceInsets, useSporeColors } from 'ui/src' import { spacing } from 'ui/src/theme' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' +import { NftView } from 'wallet/src/components/nfts/NftView' import { NftsList } from 'wallet/src/components/nfts/NftsList' import { NFTItem } from 'wallet/src/features/nfts/types' import { ModalName } from 'wallet/src/telemetry/constants' diff --git a/apps/mobile/src/data/usePersistedApolloClient.tsx b/apps/mobile/src/data/usePersistedApolloClient.tsx index e04b59f1dab..b9810e0b036 100644 --- a/apps/mobile/src/data/usePersistedApolloClient.tsx +++ b/apps/mobile/src/data/usePersistedApolloClient.tsx @@ -8,8 +8,8 @@ import { sendMobileAnalyticsEvent } from 'src/features/telemetry' import { MobileEventName } from 'src/features/telemetry/constants' import { selectCustomEndpoint } from 'src/features/tweaks/selectors' import { uniswapUrls } from 'uniswap/src/constants/urls' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { isNonJestDev } from 'utilities/src/environment' import { logger } from 'utilities/src/logger/logger' import { useAsyncData } from 'utilities/src/react/hooks' diff --git a/apps/mobile/src/features/deepLinking/README.md b/apps/mobile/src/features/deepLinking/README.md index c6948b86d25..700c96361c3 100644 --- a/apps/mobile/src/features/deepLinking/README.md +++ b/apps/mobile/src/features/deepLinking/README.md @@ -1,6 +1,6 @@ # Universal Links -Universal links allow 3rd parties to prompt the app to open to specific screens when it is installed on their device. If the app isn't installed it will open that page in Safari (a 404 on uniswap.org in this case). All universal links must use the the prefix `https://uniswap.org/app`. +Universal links allow 3rd parties to prompt the app to open to specific screens when it is installed on their device. If the app isn't installed it will open that page in Safari (a 404 on uniswap.org in this case). All universal links must use the prefix `https://uniswap.org/app`. ## Supported Screens diff --git a/apps/mobile/src/features/deepLinking/handleDeepLinkSaga.ts b/apps/mobile/src/features/deepLinking/handleDeepLinkSaga.ts index 57112eccf54..b977e0d44cb 100644 --- a/apps/mobile/src/features/deepLinking/handleDeepLinkSaga.ts +++ b/apps/mobile/src/features/deepLinking/handleDeepLinkSaga.ts @@ -27,7 +27,7 @@ import { Screens } from 'src/screens/Screens' import { Statsig } from 'statsig-react-native' import { call, put, takeLatest } from 'typed-redux-saga' import { UNISWAP_APP_HOSTNAME } from 'uniswap/src/constants/urls' -import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' +import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags' import i18n from 'uniswap/src/i18n/i18n' import { logger } from 'utilities/src/logger/logger' import { selectExtensionOnboardingState } from 'wallet/src/features/behaviorHistory/selectors' diff --git a/apps/mobile/src/features/externalProfile/ProfileHeader.tsx b/apps/mobile/src/features/externalProfile/ProfileHeader.tsx index 4cbacd1b2e6..2b1ae8f4d06 100644 --- a/apps/mobile/src/features/externalProfile/ProfileHeader.tsx +++ b/apps/mobile/src/features/externalProfile/ProfileHeader.tsx @@ -27,8 +27,8 @@ import { } from 'ui/src' import { ENS_LOGO } from 'ui/src/assets' import { iconSizes, imageSizes } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { useENSDescription, useENSName, useENSTwitterUsername } from 'wallet/src/features/ens/api' import { selectWatchedAddressSet } from 'wallet/src/features/favorites/selectors' diff --git a/apps/mobile/src/features/fiatOnRamp/FiatOnRampModal.tsx b/apps/mobile/src/features/fiatOnRamp/FiatOnRampModal.tsx index ab68d368a0c..9b4b26f0223 100644 --- a/apps/mobile/src/features/fiatOnRamp/FiatOnRampModal.tsx +++ b/apps/mobile/src/features/fiatOnRamp/FiatOnRampModal.tsx @@ -131,6 +131,14 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { useTimeout( async () => { if (fiatOnRampHostUrl) { + if (currency?.moonpayCurrencyCode) { + sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampWidgetOpened, { + externalTransactionId, + serviceProvider: 'MOONPAY', + fiatCurrency: moonpaySupportedFiatCurrency.code.toLowerCase(), + cryptoCurrency: currency.moonpayCurrencyCode.toLowerCase(), + }) + } await openUri(fiatOnRampHostUrl) dispatchAddTransaction() onClose() @@ -241,7 +249,6 @@ function FiatOnRampContent({ onClose }: { onClose: () => void }): JSX.Element { /> )} (TransactionStep.FORM) @@ -89,9 +92,8 @@ export function TransferFlow({ prefilledState, onClose }: TransferFormProps): JS ) const transferTxWithGasSettings = useMemo( - (): providers.TransactionRequest | undefined => - gasFee?.params ? { ...txRequest, ...gasFee.params } : txRequest, - [gasFee?.params, txRequest] + (): providers.TransactionRequest => ({ ...txRequest, ...gasFee.params }), + [gasFee.params, txRequest] ) const gasWarning = useTransactionGasWarning({ diff --git a/apps/mobile/src/features/unitags/ClaimUnitagScreen.tsx b/apps/mobile/src/features/unitags/ClaimUnitagScreen.tsx index aeeb6a2bdab..50e7f8620d6 100644 --- a/apps/mobile/src/features/unitags/ClaimUnitagScreen.tsx +++ b/apps/mobile/src/features/unitags/ClaimUnitagScreen.tsx @@ -297,7 +297,7 @@ export function ClaimUnitagScreen({ navigation, route }: Props): JSX.Element { borderWidth={0} fontFamily="$heading" fontSize={fontSize} - fontWeight="$large" + fontWeight="$medium" numberOfLines={1} p="$none" placeholder={inputPlaceholder} diff --git a/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx b/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx index 4fb269135aa..6573a6aca6e 100644 --- a/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx +++ b/apps/mobile/src/features/unitags/EditUnitagProfileScreen.tsx @@ -28,8 +28,8 @@ import { } from 'ui/src' import { borderRadii, fonts, iconSizes, imageSizes, spacing } from 'ui/src/theme' import { useExtractedColors } from 'ui/src/utils/colors' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { useUnitagUpdater } from 'uniswap/src/features/unitags/context' import { ProfileMetadata } from 'uniswap/src/features/unitags/types' import { isIOS } from 'uniswap/src/utils/platform' diff --git a/apps/mobile/src/features/wallet/hooks.ts b/apps/mobile/src/features/wallet/hooks.ts index be8b623ba85..1ea061b0626 100644 --- a/apps/mobile/src/features/wallet/hooks.ts +++ b/apps/mobile/src/features/wallet/hooks.ts @@ -2,8 +2,8 @@ import { useCallback, useEffect, useState } from 'react' import { useAppSelector } from 'src/app/hooks' import { openModal } from 'src/features/modals/modalSlice' import { selectModalState } from 'src/features/modals/selectModalState' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { logger } from 'utilities/src/logger/logger' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' import { useNativeAccountExists } from 'wallet/src/features/wallet/hooks' diff --git a/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts b/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts index b1bbd488448..91b808c6020 100644 --- a/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts +++ b/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts @@ -82,7 +82,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams Accept: 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({ method: 'eth_sendTransaction', response: signature }), + body: JSON.stringify({ method: 'eth_sendTransaction', response: signature, chainId }), // TODO: consider adding analytics to track UwuLink usage }).catch((error) => logger.error(error, { @@ -107,7 +107,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams type: AppNotificationType.WalletConnect, event: WalletConnectEvent.TransactionFailed, dappName: params.dapp.name, - imageUrl: params.dapp.icon, + imageUrl: params.dapp.icon ?? null, chainId, address: account.address, }) diff --git a/apps/mobile/src/features/walletConnect/walletConnectSlice.ts b/apps/mobile/src/features/walletConnect/walletConnectSlice.ts index cb3bc3e413d..49cd0308630 100644 --- a/apps/mobile/src/features/walletConnect/walletConnectSlice.ts +++ b/apps/mobile/src/features/walletConnect/walletConnectSlice.ts @@ -6,6 +6,7 @@ import { EthMethod, EthSignMethod, EthTransaction, + UwULinkMethod, } from 'wallet/src/features/walletConnect/types' export type WalletConnectPendingSession = { @@ -45,11 +46,24 @@ export interface TransactionRequest extends BaseRequest { transaction: EthTransaction } -export type WalletConnectRequest = SignRequest | TransactionRequest +export interface UwuLinkErc20Request extends BaseRequest { + type: UwULinkMethod.Erc20Send + recipient: { + address: string + name: string + } + tokenAddress: string + amount: string + isStablecoin: boolean + transaction: EthTransaction // the formatted transaction, prepared by the wallet +} + +export type WalletConnectRequest = SignRequest | TransactionRequest | UwuLinkErc20Request export const isTransactionRequest = ( request: WalletConnectRequest -): request is TransactionRequest => request.type === EthMethod.EthSendTransaction +): request is TransactionRequest => + request.type === EthMethod.EthSendTransaction || request.type === UwULinkMethod.Erc20Send export interface WalletConnectState { byAccount: { diff --git a/apps/mobile/src/polyfills/index.ts b/apps/mobile/src/polyfills/index.ts index 5366f6c1346..80f19c07119 100644 --- a/apps/mobile/src/polyfills/index.ts +++ b/apps/mobile/src/polyfills/index.ts @@ -8,7 +8,7 @@ // Import the crypto getRandomValues shim BEFORE ethers shims import 'react-native-get-random-values' -// Import the the ethers shims BEFORE ethers +// Import the ethers shims BEFORE ethers import '@ethersproject/shims' // Add .at() method to Array if necessary (missing before iOS 15) import 'src/polyfills/arrayAt' diff --git a/apps/mobile/src/screens/FiatOnRampConnecting.tsx b/apps/mobile/src/screens/FiatOnRampConnecting.tsx index 3149a257b86..677415e536f 100644 --- a/apps/mobile/src/screens/FiatOnRampConnecting.tsx +++ b/apps/mobile/src/screens/FiatOnRampConnecting.tsx @@ -120,7 +120,7 @@ export function FiatOnRampConnectingScreen({ navigation }: Props): JSX.Element | sendWalletAnalyticsEvent(FiatOnRampEventName.FiatOnRampWidgetOpened, { externalTransactionId, serviceProvider: serviceProvider.serviceProvider, - preselectedServiceProvider: serviceProvider.serviceProvider, + preselectedServiceProvider: quotesSections?.[0]?.data?.[0]?.serviceProvider, countryCode, countryState, fiatCurrency: baseCurrencyInfo?.code.toLowerCase(), diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index 0c764bbc815..a8d538cec80 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -67,8 +67,8 @@ import BuyIcon from 'ui/src/assets/icons/buy.svg' import ScanIcon from 'ui/src/assets/icons/scan-home.svg' import SendIcon from 'ui/src/assets/icons/send-action.svg' import { iconSizes, spacing } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { useInterval, useTimeout } from 'utilities/src/time/timing' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' diff --git a/apps/mobile/src/screens/Import/RestoreCloudBackupScreen.tsx b/apps/mobile/src/screens/Import/RestoreCloudBackupScreen.tsx index 788d43c55d0..aa2b06ec3d0 100644 --- a/apps/mobile/src/screens/Import/RestoreCloudBackupScreen.tsx +++ b/apps/mobile/src/screens/Import/RestoreCloudBackupScreen.tsx @@ -11,8 +11,8 @@ import { OnboardingScreens } from 'src/screens/Screens' import { useAddBackButton } from 'src/utils/useAddBackButton' import { Flex, Icons, Text, TouchableArea, Unicon, UniconV2, useIsDarkMode } from 'ui/src' import { iconSizes } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' import { FORMAT_DATE_TIME_SHORT, diff --git a/apps/mobile/src/screens/NFTItemScreen.tsx b/apps/mobile/src/screens/NFTItemScreen.tsx index 305c725bb59..ed2d7671495 100644 --- a/apps/mobile/src/screens/NFTItemScreen.tsx +++ b/apps/mobile/src/screens/NFTItemScreen.tsx @@ -13,7 +13,6 @@ import { Loader } from 'src/components/loading' import { LongMarkdownText } from 'src/components/text/LongMarkdownText' import { selectModalState } from 'src/features/modals/selectModalState' import { PriceAmount } from 'src/features/nfts/collection/ListPriceCard' -import { useNFTMenu } from 'src/features/nfts/hooks' import { BlurredImageBackground } from 'src/features/nfts/item/BlurredImageBackground' import { CollectionPreviewCard } from 'src/features/nfts/item/CollectionPreviewCard' import { NFTTraitList } from 'src/features/nfts/item/traits' @@ -43,6 +42,7 @@ import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { PollingInterval } from 'wallet/src/constants/misc' import { NFTViewer } from 'wallet/src/features/images/NFTViewer' import { GQLNftAsset } from 'wallet/src/features/nfts/hooks' +import { useNFTContextMenu } from 'wallet/src/features/nfts/useNftContextMenu' import { pushNotification } from 'wallet/src/features/notifications/slice' import { AppNotificationType, CopyNotificationType } from 'wallet/src/features/notifications/types' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' @@ -422,7 +422,7 @@ function RightElement({ }): JSX.Element { const colors = useSporeColors() - const { menuActions, onContextMenuPress, onlyShare } = useNFTMenu({ + const { menuActions, onContextMenuPress, onlyShare } = useNFTContextMenu({ contractAddress: asset?.nftContract?.address, tokenId: asset?.tokenId, owner, diff --git a/apps/mobile/src/screens/Onboarding/LandingScreen.tsx b/apps/mobile/src/screens/Onboarding/LandingScreen.tsx index c0f90bb948b..f6129d3e504 100644 --- a/apps/mobile/src/screens/Onboarding/LandingScreen.tsx +++ b/apps/mobile/src/screens/Onboarding/LandingScreen.tsx @@ -12,8 +12,8 @@ import { OnboardingScreens, UnitagScreens } from 'src/screens/Screens' import { hideSplashScreen } from 'src/utils/splashScreen' import { isDevBuild } from 'src/utils/version' import { Button, Flex, HapticFeedback, Text, TouchableArea, useIsDarkMode } from 'ui/src' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { useTimeout } from 'utilities/src/time/timing' import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types' import { useCanAddressClaimUnitag } from 'wallet/src/features/unitags/hooks' diff --git a/apps/mobile/src/screens/Onboarding/QRAnimation/QRAnimation.tsx b/apps/mobile/src/screens/Onboarding/QRAnimation/QRAnimation.tsx index 8938cd07af9..25f5354f287 100644 --- a/apps/mobile/src/screens/Onboarding/QRAnimation/QRAnimation.tsx +++ b/apps/mobile/src/screens/Onboarding/QRAnimation/QRAnimation.tsx @@ -51,8 +51,8 @@ import { ONBOARDING_QR_ETCHING_VIDEO_DARK, ONBOARDING_QR_ETCHING_VIDEO_LIGHT } f import LockIcon from 'ui/src/assets/icons/lock.svg' import { AnimatedFlex, flexStyles } from 'ui/src/components/layout' import { fonts, iconSizes, opacify, spacing } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { QRCodeDisplay } from 'wallet/src/components/QRCodeScanner/QRCode' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { Arrow } from 'wallet/src/components/icons/Arrow' diff --git a/apps/mobile/src/screens/SettingsScreen.tsx b/apps/mobile/src/screens/SettingsScreen.tsx index 0d2a92c9c69..5fccc64316f 100644 --- a/apps/mobile/src/screens/SettingsScreen.tsx +++ b/apps/mobile/src/screens/SettingsScreen.tsx @@ -47,8 +47,8 @@ import MessageQuestion from 'ui/src/assets/icons/message-question.svg' import UniswapIcon from 'ui/src/assets/icons/uniswap-logo.svg' import { iconSizes, spacing } from 'ui/src/theme' import { uniswapUrls } from 'uniswap/src/constants/urls' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' import { isAndroid } from 'uniswap/src/utils/platform' import { ONE_SECOND_MS } from 'utilities/src/time/time' diff --git a/apps/mobile/src/screens/SettingsWallet.tsx b/apps/mobile/src/screens/SettingsWallet.tsx index 2eb96bff566..a9e1dd4031e 100644 --- a/apps/mobile/src/screens/SettingsWallet.tsx +++ b/apps/mobile/src/screens/SettingsWallet.tsx @@ -34,8 +34,8 @@ import NotificationIcon from 'ui/src/assets/icons/bell.svg' import GlobalIcon from 'ui/src/assets/icons/global.svg' import TextEditIcon from 'ui/src/assets/icons/textEdit.svg' import { iconSizes, spacing } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { Switch } from 'wallet/src/components/buttons/Switch' import { ChainId } from 'wallet/src/constants/chains' diff --git a/apps/mobile/src/screens/SettingsWalletEdit.tsx b/apps/mobile/src/screens/SettingsWalletEdit.tsx index b4372cab28c..0986cb72c17 100644 --- a/apps/mobile/src/screens/SettingsWalletEdit.tsx +++ b/apps/mobile/src/screens/SettingsWalletEdit.tsx @@ -14,8 +14,8 @@ import { Screen } from 'src/components/layout/Screen' import { UnitagBanner } from 'src/components/unitags/UnitagBanner' import { Button, Flex, Icons, Text } from 'ui/src' import { fonts } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { isIOS } from 'uniswap/src/utils/platform' import { TextInput } from 'wallet/src/components/input/TextInput' import { NICKNAME_MAX_LENGTH } from 'wallet/src/constants/accounts' diff --git a/apps/web/cypress/e2e/mini-portfolio/uniTags.test.ts b/apps/web/cypress/e2e/mini-portfolio/uniTags.test.ts index 341dcef8f5f..e3ddf00add5 100644 --- a/apps/web/cypress/e2e/mini-portfolio/uniTags.test.ts +++ b/apps/web/cypress/e2e/mini-portfolio/uniTags.test.ts @@ -58,13 +58,11 @@ describe('Uni tags support', () => { cy.contains(haydenUnitag).should('be.visible') cy.contains('0x50EC...79C3').should('be.visible') }) - cy.get(getTestSelector('secondary-identifiers')) - .trigger('mouseover') - .click() - .within(() => { - cy.contains(haydenENS).should('be.visible') - cy.contains('0x50EC...79C3').should('be.visible') - }) + cy.get(getTestSelector('secondary-identifiers')).trigger('mouseover').click() + cy.get(getTestSelector('secondary-identifiers-dropdown')).within(() => { + cy.contains(haydenENS) + cy.contains('0x50EC...79C3') + }) }) }) }) diff --git a/apps/web/cypress/e2e/permit2.test.ts b/apps/web/cypress/e2e/permit2.test.ts index 3ffea2e3a55..8cfd82e7ad0 100644 --- a/apps/web/cypress/e2e/permit2.test.ts +++ b/apps/web/cypress/e2e/permit2.test.ts @@ -106,7 +106,8 @@ describe('Permit2', () => { * already 0 to mitigate the race condition described here: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 */ - it('swaps USDT with existing permit, and existing but insufficient token approval', () => { + // TODO re-enable web test + it.skip('swaps USDT with existing permit, and existing but insufficient token approval', () => { cy.hardhat().then(async (hardhat) => { await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDT, 2e6)) await hardhat.mine() diff --git a/apps/web/cypress/e2e/wallet-dropdown.test.ts b/apps/web/cypress/e2e/wallet-dropdown.test.ts index e8068e00087..58f06f6ad66 100644 --- a/apps/web/cypress/e2e/wallet-dropdown.test.ts +++ b/apps/web/cypress/e2e/wallet-dropdown.test.ts @@ -1,4 +1,4 @@ -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' import { getTestSelector } from '../utils' describe('Wallet Dropdown', () => { diff --git a/apps/web/cypress/support/commands.ts b/apps/web/cypress/support/commands.ts index dcba49670db..97b37e4528b 100644 --- a/apps/web/cypress/support/commands.ts +++ b/apps/web/cypress/support/commands.ts @@ -2,7 +2,7 @@ import 'cypress-hardhat/lib/browser' import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge' -import { FeatureFlagClient, FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' +import { FeatureFlagClient, FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags' import { UserState, initialState } from '../../src/state/user/reducer' import { CONNECTED_WALLET_USER_STATE, setInitialUserState } from '../utils/user-state' diff --git a/apps/web/functions/[[index]].ts b/apps/web/functions/[[index]].ts index 5ae1be39c18..1991b5164db 100644 --- a/apps/web/functions/[[index]].ts +++ b/apps/web/functions/[[index]].ts @@ -4,7 +4,7 @@ /* eslint-disable import/no-unused-modules */ import { paths } from '../src/pages/paths' -import { MetaTagInjector } from './components/metaTagInjector' +import { transformResponse } from './utils/transformResponse' function doesMatchPath(path: string): boolean { const regexPaths = paths.map((p) => '^' + p.replace(/:[^/]+/g, '[^/]+').replace(/\*/g, '.*') + '$') @@ -21,13 +21,13 @@ export const onRequest: PagesFunction = async ({ request, next }) => { url: request.url, description: 'Swap or provide liquidity on the Uniswap Protocol', } - const res = next() + const response = next() if (doesMatchPath(requestURL.pathname)) { try { - return new HTMLRewriter().on('head', new MetaTagInjector(data, request)).transform(await res) + return transformResponse(request, await response, data) } catch (e) { - return res + return response } } - return res + return response } diff --git a/apps/web/functions/components/metaTagInjector.ts b/apps/web/functions/components/metaTagInjector.ts index 2be810a8211..9bf69f8575d 100644 --- a/apps/web/functions/components/metaTagInjector.ts +++ b/apps/web/functions/components/metaTagInjector.ts @@ -10,6 +10,8 @@ type MetaTagInjectorInput = { * to inject meta tags into the of an HTML document. */ export class MetaTagInjector implements HTMLRewriterElementContentHandlers { + static SELECTOR = 'head' + constructor(private input: MetaTagInjectorInput, private request: Request) {} append(element: Element, property: string, content: string) { diff --git a/apps/web/functions/explore/tokens/[[index]].ts b/apps/web/functions/explore/tokens/[[index]].ts index 88ee15fcce2..e5dada0841a 100644 --- a/apps/web/functions/explore/tokens/[[index]].ts +++ b/apps/web/functions/explore/tokens/[[index]].ts @@ -1,18 +1,18 @@ /* eslint-disable import/no-unused-modules */ -import { getMetadataRequest } from '../../utils/getRequest' import getToken from '../../utils/getToken' +import { transformResponse } from '../../utils/transformResponse' export const onRequest: PagesFunction = async ({ params, request, next }) => { - const res = next() + const response = next() try { const { index } = params const networkName = index[0]?.toString() const tokenAddress = index[1]?.toString() if (!tokenAddress) { - return res + return response } - return getMetadataRequest(res, request, () => getToken(networkName, tokenAddress, request.url)) + return transformResponse(request, await response, () => getToken(networkName, tokenAddress, request.url)) } catch (e) { - return res + return response } } diff --git a/apps/web/functions/nfts/asset/[[index]].ts b/apps/web/functions/nfts/asset/[[index]].ts index c7a15a640cf..89c05a7babe 100644 --- a/apps/web/functions/nfts/asset/[[index]].ts +++ b/apps/web/functions/nfts/asset/[[index]].ts @@ -1,15 +1,15 @@ /* eslint-disable import/no-unused-modules */ import getAsset from '../../utils/getAsset' -import { getMetadataRequest } from '../../utils/getRequest' +import { transformResponse } from '../../utils/transformResponse' export const onRequest: PagesFunction = async ({ params, request, next }) => { - const res = next() + const response = next() try { const { index } = params const collectionAddress = index[0]?.toString() const tokenId = index[1]?.toString() - return getMetadataRequest(res, request, () => getAsset(collectionAddress, tokenId, request.url)) + return transformResponse(request, await response, () => getAsset(collectionAddress, tokenId, request.url)) } catch (e) { - return res + return response } } diff --git a/apps/web/functions/nfts/collection/[index].ts b/apps/web/functions/nfts/collection/[index].ts index 381dcca6468..0e4c29d4fd9 100644 --- a/apps/web/functions/nfts/collection/[index].ts +++ b/apps/web/functions/nfts/collection/[index].ts @@ -1,14 +1,14 @@ /* eslint-disable import/no-unused-modules */ import getCollection from '../../utils/getCollection' -import { getMetadataRequest } from '../../utils/getRequest' +import { transformResponse } from '../../utils/transformResponse' export const onRequest: PagesFunction = async ({ params, request, next }) => { - const res = next() + const response = next() try { const { index } = params const collectionAddress = index?.toString() - return getMetadataRequest(res, request, () => getCollection(collectionAddress, request.url)) + return transformResponse(request, await response, () => getCollection(collectionAddress, request.url)) } catch (e) { - return res + return response } } diff --git a/apps/web/functions/utils/getRequest.ts b/apps/web/functions/utils/getRequest.ts index f1b88cc5c21..9984fb407f1 100644 --- a/apps/web/functions/utils/getRequest.ts +++ b/apps/web/functions/utils/getRequest.ts @@ -1,23 +1,5 @@ -import { MetaTagInjector } from '../components/metaTagInjector' import Cache, { Data } from './cache' -export async function getMetadataRequest( - res: Promise, - request: Request, - getData: () => Promise -) { - try { - const cachedData = await getRequest(request.url, getData, (data): data is Data => true) - if (cachedData) { - return new HTMLRewriter().on('head', new MetaTagInjector(cachedData, request)).transform(await res) - } else { - return res - } - } catch (e) { - return res - } -} - export async function getRequest( url: string, getData: () => Promise, diff --git a/apps/web/functions/utils/transformResponse.ts b/apps/web/functions/utils/transformResponse.ts new file mode 100644 index 00000000000..18d7ef34a77 --- /dev/null +++ b/apps/web/functions/utils/transformResponse.ts @@ -0,0 +1,23 @@ +/* eslint-disable import/no-unused-modules */ +import { MetaTagInjector } from '../components/metaTagInjector' +import { Data } from './cache' +import { getRequest } from './getRequest' + +export async function transformResponse( + request: Request, + response: Response, + data: (() => Promise) | Data | undefined +) { + try { + if (typeof data === 'function') { + data = await getRequest(request.url, data, (data): data is Data => true) + } + if (data) { + return new HTMLRewriter().on(MetaTagInjector.SELECTOR, new MetaTagInjector(data, request)).transform(response) + } else { + return response + } + } catch (e) { + return response + } +} diff --git a/apps/web/package.json b/apps/web/package.json index 106f3b96018..f84eb602615 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -155,7 +155,7 @@ "ts-jest": "^29.1.1", "tsafe": "1.6.4", "typescript": "5.3.3", - "webpack": "5.89.0", + "webpack": "5.90.0", "webpack-retry-chunk-load-plugin": "3.1.1", "wrangler": "3.15.0", "yarn-deduplicate": "6.0.0" @@ -184,6 +184,7 @@ "@sentry/types": "7.80.0", "@tamagui/core": "1.94.3", "@tamagui/react-native-svg": "1.94.3", + "@tanstack/react-query": "5.28.14", "@tanstack/react-table": "8.10.7", "@types/poisson-disk-sampling": "2.2.4", "@types/react-scroll-sync": "0.8.7", @@ -290,6 +291,8 @@ "utilities": "workspace:^", "uuid": "9.0.0", "video-extensions": "1.2.0", + "viem": "2.x", + "wagmi": "2.5.19", "wcag-contrast": "3.0.0", "web-vitals": "2.1.4", "xml2js": "0.6.2", diff --git a/apps/web/public/csp.json b/apps/web/public/csp.json index 77a085a8a6e..526e8f3e0c5 100644 --- a/apps/web/public/csp.json +++ b/apps/web/public/csp.json @@ -27,7 +27,7 @@ "https://*.uniswap.org", "https://uniswap.org", "https://assets.coingecko.com/", - "https://*.amazonaws.com", + "https://*.amazonaws.com", "https://basescan.org", "https://celo-org.github.io/", "https://cdn.center.app/", @@ -60,43 +60,63 @@ "wss://*.uniswap.org", "https://*.uniswap.org", "https://uniswap.org", - "https://assets.coingecko.com/", + "https://arb1.arbitrum.io", + "https://*.coingecko.com/", + "https://*.alchemy.com", "https://buy.moonpay.com/", + "https://bsc-dataseed1.binance.org/", "https://cdn.center.app/", "https://cdn.jsdelivr.net/npm/@rive-app/canvas@2.8.3/rive.wasm", - "https://chain-proxy.wallet.coinbase.com", + "https://*.coinbase.com", "https://statsigapi.net", "https://api.moonpay.com/", "https://api.opensea.io", "https://api.thegraph.com/", "https://arbitrum-mainnet.infura.io/", + "https://assets.coingecko.com", "https://avalanche-mainnet.infura.io/", "https://base-mainnet.infura.io/", "https://bridge.arbitrum.io", "https://celo-mainnet.infura.io/", "https://celo-org.github.io", "https://cloudflare-ipfs.com", - "https://explorer-api.walletconnect.com", + "https://*.zerion.io", + "https://*.drpc.org/", + "https://*.base.org/", + "https://*.walletconnect.com", "https://ethereum-optimism.github.io/", + "https://*.twnodes.com", "https://forno.celo.org/", - "https://www.gemini.com", + "https://*.gemini.com", "https://gateway.ipfs.io/", "https://i.seadn.io/", + "https://ipfs.io/", "https://lh3.googleusercontent.com/", "https://mainnet.infura.io", + "https://*.nodereal.io", "https://o1037921.ingest.sentry.io", "https://old-wispy-arrow.bsc.quiknode.pro/", "https://openseauserdata.com/", - "https://optimism-mainnet.infura.io/", - "https://polygon-mainnet.infura.io/", + "https://performance.radar.cloudflare.com/", + "https://valid.rpki.cloudflare.com", + "https://sparrow.cloudflare.com/", + "https://ipv4-check-perf.radar.cloudflare.com", + "https://ipv6-check-perf.radar.cloudflare.com/", + "https://invalid.rpki.cloudflare.com/", "https://raw.githubusercontent.com", "https://raw.seadn.io/", - "https://s2.coinmarketcap.com/", - "https://static.optimism.io", + "https://rpc.ankr.com", + "https://rpc.degen.tips", + "https://rpc-mainnet.maticvigil.com", + "https://rpc.mevblocker.io/", + "https://rpc.scroll.io/", + "https://*.coinmarketcap.com/", + "https://*.optimism.io", "https://sockjs-us3.pusher.com/", "https://api.studio.thegraph.com/", + "https://*.googleapis.com", "https://trustwallet.com", - "https://tokenlist.arbitrum.io", + "https://*.arbitrum.io", "https://tokens.coingecko.com", "https://*.twnodes.com", "https://ultra-blue-flower.quiknode.pro", @@ -104,10 +124,13 @@ "https://us-central1-uniswap-mobile.cloudfunctions.net/", "https://vercel.com", "https://vercel.live/", + "https://wallet.crypto.com", + "https://web3.1inch.io", "https://www.gemini.com", "https://*.quiknode.pro", "https://*.infura.io", "wss://relay.walletconnect.com", + "wss://relay.walletconnect.org", "wss://www.walletlink.org", "wss://ws-us3.pusher.com/" ], diff --git a/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx b/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx index c1440c7457e..7cd8fd56e91 100644 --- a/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx +++ b/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx @@ -65,6 +65,7 @@ const UNIButton = styled(WalletButton)` const IconContainer = styled.div` display: flex; + flex: 0 0 auto; align-items: center; & > a, & > button { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.test.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.test.tsx index 2bc3c95f35a..1904fc1977a 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.test.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.test.tsx @@ -1,30 +1,21 @@ +import 'test-utils/tokens/mocks' + import { ChainId, WETH9 } from '@uniswap/sdk-core' import { formatTimestamp } from 'components/AccountDrawer/MiniPortfolio/formatTimestamp' -import { DAI, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens' -import { useCurrency } from 'hooks/Tokens' +import { DAI } from 'constants/tokens' import { SignatureType } from 'state/signatures/types' import { mocked } from 'test-utils/mocked' import { render } from 'test-utils/render' -import { UniswapXOrderStatus } from 'types/uniswapx' +import { UniswapXOrderStatus } from 'types/uniswapx' import { OrderContent } from './OffchainActivityModal' -jest.mock('hooks/Tokens', () => ({ - useCurrency: jest.fn(), -})) jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => ({ formatTimestamp: jest.fn(), })) describe('OrderContent', () => { beforeEach(() => { - mocked(useCurrency).mockImplementation((currencyId: Maybe) => { - if (currencyId === WETH9[ChainId.MAINNET].address) { - return WRAPPED_NATIVE_CURRENCY[ChainId.MAINNET] - } else { - return DAI - } - }) mocked(formatTimestamp).mockImplementation(() => { return 'Mock Date' // This ensures consistent test behavior across local and CI }) @@ -45,7 +36,7 @@ describe('OrderContent', () => { isUniswapXOrder: true, type: 1, tradeType: 0, - inputCurrencyId: '0x6b175474e89094c44da98b954eedeac495271d0f', + inputCurrencyId: DAI.address, outputCurrencyId: WETH9[ChainId.MAINNET].address, inputCurrencyAmountRaw: '252074033564766400000', expectedOutputCurrencyAmountRaw: '106841079134757921', diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx index aa145203ef7..f4e33aff381 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal.tsx @@ -101,7 +101,7 @@ const InsufficientFundsCopyContainer = styled(Row)` const AlertIconContainer = styled.div` display: flex; flex-shrink: 0; - background-color: #1f1e02; + background-color: ${({ theme }) => theme.deprecated_accentWarningSoft}; width: 40px; height: 40px; justify-content: center; diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap index 6c4fac85673..51c1bd73f6c 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/parseRemote.test.tsx.snap @@ -66,7 +66,46 @@ Object { } `; -exports[`parseRemote parseRemoteActivities should parse closed UniswapX order 1`] = ` +exports[`parseRemote parseRemoteActivities should parse eth wrap 1`] = ` +Object { + "chainId": 1, + "currencies": Array [ + ExtendedEther { + "chainId": 1, + "decimals": 18, + "isNative": true, + "isToken": false, + "name": "Ethereum", + "symbol": "ETH", + }, + Token { + "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "buyFeeBps": undefined, + "chainId": 1, + "decimals": 18, + "isNative": false, + "isToken": true, + "name": "Wrapped Ether", + "sellFeeBps": undefined, + "symbol": "WETH", + }, + ], + "descriptor": "100 ETH for 100 WETH", + "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3", + "hash": "someHash", + "isSpam": false, + "logos": Array [ + "https://token-icons.s3.amazonaws.com/eth.png", + "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + ], + "nonce": 12345, + "status": "CONFIRMED", + "timestamp": 10000, + "title": "Wrapped", +} +`; + +exports[`parseRemote parseRemoteActivities should parse expired UniswapX order 1`] = ` Object { "chainId": 1, "currencies": Array [ @@ -104,6 +143,7 @@ Object { "addedTime": 10000, "chainId": 1, "encodedOrder": undefined, + "expiry": undefined, "id": "someId", "offerer": "someOfferer", "orderHash": "someHash", @@ -115,11 +155,11 @@ Object { "isUniswapXOrder": true, "minimumOutputCurrencyAmountRaw": "200000000000000000000", "outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "settledOutputCurrencyAmountRaw": "200000000000000000000", + "settledOutputCurrencyAmountRaw": undefined, "tradeType": 0, "type": 1, }, - "txHash": "someHash", + "txHash": undefined, "type": undefined, }, "prefixIconSrc": "bolt.svg", @@ -130,17 +170,20 @@ Object { } `; -exports[`parseRemote parseRemoteActivities should parse eth wrap 1`] = ` +exports[`parseRemote parseRemoteActivities should parse filledUniswapX order 1`] = ` Object { "chainId": 1, "currencies": Array [ - ExtendedEther { + Token { + "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "buyFeeBps": undefined, "chainId": 1, "decimals": 18, - "isNative": true, - "isToken": false, - "name": "Ethereum", - "symbol": "ETH", + "isNative": false, + "isToken": true, + "name": "DAI", + "sellFeeBps": undefined, + "symbol": "DAI", }, Token { "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", @@ -154,18 +197,40 @@ Object { "symbol": "WETH", }, ], - "descriptor": "100 ETH for 100 WETH", - "from": "0x50EC05ADe8280758E2077fcBC08D878D4aef79C3", + "descriptor": "100 DAI for 200 WETH", + "from": "someOfferer", "hash": "someHash", - "isSpam": false, "logos": Array [ - "https://token-icons.s3.amazonaws.com/eth.png", - "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png", + "someUrl", + "someUrl", ], - "nonce": 12345, + "offchainOrderDetails": Object { + "addedTime": 10000, + "chainId": 1, + "encodedOrder": undefined, + "expiry": undefined, + "id": "someId", + "offerer": "someOfferer", + "orderHash": "someHash", + "status": "filled", + "swapInfo": Object { + "expectedOutputCurrencyAmountRaw": "200000000000000000000", + "inputCurrencyAmountRaw": "100000000000000000000", + "inputCurrencyId": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "isUniswapXOrder": true, + "minimumOutputCurrencyAmountRaw": "200000000000000000000", + "outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "settledOutputCurrencyAmountRaw": "200000000000000000000", + "tradeType": 0, + "type": 1, + }, + "txHash": "someHash", + "type": undefined, + }, + "prefixIconSrc": "bolt.svg", "status": "CONFIRMED", "timestamp": 10000, - "title": "Wrapped", + "title": "Swapped", } `; diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts index 463f9c1c0b9..67a1064fe04 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/fixtures/activity.ts @@ -63,56 +63,6 @@ const mockAssetActivityPartsFragment = { }, } -const mockSwapOrderDetailsPartsFragment = { - __typename: 'SwapOrderDetails', - id: 'someId', - offerer: 'someOfferer', - hash: 'someHash', - inputTokenQuantity: '100', - outputTokenQuantity: '200', - orderStatus: SwapOrderStatus.Open, - inputToken: { - __typename: 'Token', - id: DAI.address, - name: 'DAI', - symbol: DAI.symbol, - address: DAI.address, - decimals: 18, - chain: Chain.Ethereum, - standard: TokenStandard.Erc20, - project: { - __typename: 'TokenProject', - id: 'projectId', - isSpam: false, - logo: { - __typename: 'Image', - id: 'imageId', - url: 'someUrl', - }, - }, - }, - outputToken: { - __typename: 'Token', - id: WETH9[1].address, - name: 'Wrapped Ether', - symbol: 'WETH', - address: WETH9[1].address, - decimals: 18, - chain: Chain.Ethereum, - standard: TokenStandard.Erc20, - project: { - __typename: 'TokenProject', - id: 'projectId', - isSpam: false, - logo: { - __typename: 'Image', - id: 'imageId', - url: 'someUrl', - }, - }, - }, -} - const mockNftApprovalPartsFragment: NftApprovalPartsFragment = { __typename: 'NftApproval', id: 'approvalId', @@ -395,19 +345,6 @@ const mockTokenApprovalPartsFragment: TokenApprovalPartsFragment = { }, } -export const MockOpenUniswapXOrder = { - ...mockAssetActivityPartsFragment, - details: mockSwapOrderDetailsPartsFragment, -} as AssetActivityPartsFragment - -export const MockClosedUniswapXOrder = { - ...mockAssetActivityPartsFragment, - details: { - ...mockSwapOrderDetailsPartsFragment, - orderStatus: SwapOrderStatus.Expired, - }, -} as AssetActivityPartsFragment - const commonTransactionDetailsFields = { __typename: 'TransactionDetails', from: MockSenderAddress, diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx index 92164dcb5d2..6e5b8451af6 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.test.tsx @@ -1,15 +1,14 @@ import { act, renderHook } from '@testing-library/react' import ms from 'ms' +import { MockExpiredUniswapXOrder, MockFilledUniswapXOrder, MockOpenUniswapXOrder } from 'state/signatures/fixtures' import { - MockClosedUniswapXOrder, MockMint, MockMoonpayPurchase, MockNFTApproval, MockNFTApprovalForAll, MockNFTPurchase, MockNFTReceive, - MockOpenUniswapXOrder, MockRemoveLiquidity, MockSenderAddress, MockSpamMint, @@ -19,10 +18,10 @@ import { MockTokenApproval, MockTokenReceive, MockTokenSend, + MockWrap, mockTokenTransferInPartsFragment, mockTokenTransferOutPartsFragment, mockTransactionDetailsPartsFragment, - MockWrap, } from './fixtures/activity' import { offchainOrderDetailsFromGraphQLTransactionActivity, @@ -48,8 +47,12 @@ describe('parseRemote', () => { const result = parseRemoteActivities([MockOpenUniswapXOrder], '', jest.fn()) expect(result).toEqual({}) }) - it('should parse closed UniswapX order', () => { - const result = parseRemoteActivities([MockClosedUniswapXOrder], '', jest.fn()) + it('should parse expired UniswapX order', () => { + const result = parseRemoteActivities([MockExpiredUniswapXOrder], '', jest.fn()) + expect(result?.['someHash']).toMatchSnapshot() + }) + it('should parse filledUniswapX order', () => { + const result = parseRemoteActivities([MockFilledUniswapXOrder], '', jest.fn()) expect(result?.['someHash']).toMatchSnapshot() }) it('should parse NFT approval', () => { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx index 07f29eaca00..ea965e1bf6d 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx @@ -1,6 +1,7 @@ import { ChainId, Currency, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, TradeType, UNI_ADDRESSES } from '@uniswap/sdk-core' import UniswapXBolt from 'assets/svg/bolt.svg' import moonpayLogoSrc from 'assets/svg/moonpay.svg' +import { asSupportedChain } from 'constants/chains' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { BigNumber } from 'ethers/lib/ethers' import { formatUnits, parseUnits } from 'ethers/lib/utils' @@ -8,9 +9,8 @@ import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromG import { t } from 'i18n' import ms from 'ms' import { useEffect, useState } from 'react' -import store from 'state' -import { addSignature } from 'state/signatures/reducer' -import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types' +import { OrderActivity, parseRemote as parseRemoteSignature } from 'state/signatures/parseRemote' +import { UniswapXOrderDetails } from 'state/signatures/types' import { TransactionType as LocalTransactionType } from 'state/transactions/types' import { UniswapXOrderStatus } from 'types/uniswapx' import { @@ -19,9 +19,6 @@ import { NftApprovalPartsFragment, NftApproveForAllPartsFragment, NftTransferPartsFragment, - SwapOrderDetailsPartsFragment, - SwapOrderStatus, - SwapOrderType, TokenApprovalPartsFragment, TokenAssetPartsFragment, TokenTransferPartsFragment, @@ -29,9 +26,8 @@ import { TransactionType, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { isAddress, isSameAddress } from 'utilities/src/addresses' -import { currencyId } from 'utils/currencyId' import { NumberType, useFormatter } from 'utils/formatNumbers' -import { MOONPAY_SENDER_ADDRESSES, OrderStatusTable, OrderTextTable } from '../constants' +import { MOONPAY_SENDER_ADDRESSES, OrderTextTable } from '../constants' import { Activity } from './types' type TransactionChanges = { @@ -276,7 +272,7 @@ export function offchainOrderDetailsFromGraphQLTransactionActivity( changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType ): UniswapXOrderDetails | undefined { - const chainId = supportedChainIdFromGQLChain(activity.chain) + const chainId = asSupportedChain(supportedChainIdFromGQLChain(activity.chain)) if (!activity || !activity.details || !chainId) return undefined if (changes.TokenTransfer.length < 2) return undefined @@ -333,7 +329,6 @@ function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: For } type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment } -type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment } function parseSendReceive( changes: TransactionChanges, @@ -448,112 +443,31 @@ function getLogoSrcs(changes: TransactionChanges): Array { return Array.from(logoSet) } -function swapOrderTypeToSignatureType(swapOrderType: SwapOrderType): SignatureType { - switch (swapOrderType) { - case SwapOrderType.Limit: - return SignatureType.SIGN_LIMIT - case SwapOrderType.Dutch: - return SignatureType.SIGN_UNISWAPX_ORDER - case SwapOrderType.DutchV2: - return SignatureType.SIGN_UNISWAPX_V2_ORDER - } -} - -function parseUniswapXOrder({ details, chain, timestamp }: OrderActivity): Activity | undefined { - const supportedChain = supportedChainIdFromGQLChain(chain) - if (!supportedChain) { - logSentryErrorForUnsupportedChain({ - extras: { details }, - errorMessage: 'Invalid activity from unsupported chain received from GQL', - }) - return undefined - } - - // If the order is open, maybe add it to our local records (if it was initiated on this device, this will be a no-op). - if (details.orderStatus === SwapOrderStatus.Open) { - const inputCurrency = gqlToCurrency(details.inputToken) - const outputCurrency = gqlToCurrency(details.outputToken) - - const inputTokenQuantity = parseUnits(details.inputTokenQuantity, details.inputToken.decimals).toString() - const outputTokenQuantity = parseUnits(details.outputTokenQuantity, details.outputToken.decimals).toString() +function parseUniswapXOrder(activity: OrderActivity): Activity | undefined { + const signature = parseRemoteSignature(activity) - if (inputTokenQuantity === '0' || outputTokenQuantity === '0') { - // TODO(WEB-3765): This is a temporary mitigation for a bug where the backend sends "0.000000" for small amounts. - throw new Error('Invalid activity received from GQL') - } - - store.dispatch( - addSignature({ - type: swapOrderTypeToSignatureType(details.swapOrderType), - offerer: details.offerer, - id: details.hash, - chainId: supportedChain, - orderHash: details.hash, - expiry: details.expiry, - encodedOrder: details.encodedOrder, - swapInfo: { - type: LocalTransactionType.SWAP, - inputCurrencyId: currencyId(inputCurrency), - outputCurrencyId: currencyId(outputCurrency), - isUniswapXOrder: true, - // This doesn't affect the display, but we don't know this value from the remote activity. - tradeType: TradeType.EXACT_INPUT, - inputCurrencyAmountRaw: inputTokenQuantity, - expectedOutputCurrencyAmountRaw: outputTokenQuantity, - minimumOutputCurrencyAmountRaw: outputTokenQuantity, - }, - status: UniswapXOrderStatus.OPEN, - addedTime: timestamp * 1000, - }) - ) - return undefined + // If the order is open, do not render it. + if (signature.status === UniswapXOrderStatus.OPEN) { + return } - // If the order is not open, render it like any other remote activity. - const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity, orderStatus } = details - const uniswapXOrderStatus = OrderStatusTable[orderStatus] - const { status, statusMessage, title } = OrderTextTable[uniswapXOrderStatus] - const descriptor = getSwapDescriptor({ - tokenIn: inputToken, - inputAmount: inputTokenQuantity, - tokenOut: outputToken, - outputAmount: outputTokenQuantity, - }) - + const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity } = activity.details return { - hash: details.hash, - chainId: supportedChain, - status, - statusMessage, - offchainOrderDetails: { - id: details.id, - type: swapOrderTypeToSignatureType(details.swapOrderType), - encodedOrder: details.encodedOrder, - txHash: details.hash, - orderHash: details.hash, - offerer: details.offerer, - chainId: supportedChain, - status: uniswapXOrderStatus, - addedTime: timestamp, - swapInfo: { - isUniswapXOrder: true, - type: LocalTransactionType.SWAP, - tradeType: TradeType.EXACT_INPUT, - inputCurrencyId: inputToken.address ?? '', - outputCurrencyId: outputToken.address ?? '', - inputCurrencyAmountRaw: parseUnits(inputTokenQuantity, inputToken.decimals).toString(), - expectedOutputCurrencyAmountRaw: parseUnits(outputTokenQuantity, outputToken.decimals).toString(), - minimumOutputCurrencyAmountRaw: parseUnits(outputTokenQuantity, outputToken.decimals).toString(), - settledOutputCurrencyAmountRaw: parseUnits(outputTokenQuantity, outputToken.decimals).toString(), - }, - }, - timestamp, + hash: signature.orderHash, + chainId: signature.chainId, + offchainOrderDetails: signature, + timestamp: activity.timestamp, logos: [inputToken.project?.logo?.url, outputToken.project?.logo?.url], currencies: [gqlToCurrency(inputToken), gqlToCurrency(outputToken)], - title, - descriptor, - from: details.offerer, + descriptor: getSwapDescriptor({ + tokenIn: inputToken, + inputAmount: inputTokenQuantity, + tokenOut: outputToken, + outputAmount: outputTokenQuantity, + }), + from: signature.offerer, prefixIconSrc: UniswapXBolt, + ...OrderTextTable[signature.status], } } diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts index f4248173491..09e8c3ef6da 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts @@ -16,6 +16,7 @@ export type Activity = { title: string descriptor?: string logos?: Array + // TODO(WEB-3839): replace Currency with CurrencyInfo currencies?: Array otherAccount?: string from: string diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.test.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.test.tsx index b4f4a992d12..25cbe105083 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.test.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.test.tsx @@ -1,3 +1,5 @@ +import 'test-utils/tokens/mocks' + import { ChainId, WETH9 } from '@uniswap/sdk-core' import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types' import { LimitDetailActivityRow } from 'components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow' @@ -14,20 +16,6 @@ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => { } }) -jest.mock('hooks/Tokens', () => { - return { - useCurrency: (address?: string) => { - if (address?.toLowerCase() === DAI.address.toLowerCase()) { - return DAI - } - if (address?.toLowerCase() === WETH9[ChainId.MAINNET].address.toLowerCase()) { - return WETH9[ChainId.MAINNET] - } - return undefined - }, - } -}) - const mockOrderDetails: UniswapXOrderDetails = { type: SignatureType.SIGN_LIMIT, orderHash: '0x1234', @@ -60,6 +48,7 @@ const mockOrder: Activity = { title: 'Limit pending', from: '0x456', offchainOrderDetails: mockOrderDetails, + currencies: [DAI, WETH9[ChainId.MAINNET]], } describe('LimitDetailActivityRow', () => { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.tsx index 733c9107dc3..121f3a5d5e9 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitDetailActivityRow.tsx @@ -9,7 +9,7 @@ import { FormatType, formatTimestamp } from 'components/AccountDrawer/MiniPortfo import Column from 'components/Column' import Row from 'components/Row' import { parseUnits } from 'ethers/lib/utils' -import useTokenLogoSource from 'hooks/useAssetLogoSource' +import { useCurrencyInfo } from 'hooks/Tokens' import { useScreenSize } from 'hooks/useScreenSize' import { Trans } from 'i18n' import { Checkbox } from 'nft/components/layout/Checkbox' @@ -50,7 +50,9 @@ const CircleLogoImage = styled.img<{ size: string }>` export function LimitDetailActivityRow({ order, onToggleSelect, selected }: LimitDetailActivityRowProps) { const theme = useTheme() - const { chainId, logos, currencies, offchainOrderDetails } = order + const { logos, currencies, offchainOrderDetails } = order + const inputCurrencyInfo = useCurrencyInfo(currencies?.[0]) + const outputCurrencyInfo = useCurrencyInfo(currencies?.[1]) const openOffchainActivityModal = useOpenOffchainActivityModal() const { formatReviewSwapCurrencyAmount } = useFormatter() const [hovered, setHovered] = useState(false) @@ -70,19 +72,11 @@ export function LimitDetailActivityRow({ order, onToggleSelect, selected }: Limi ) }, [amounts?.inputAmount, amounts?.outputAmount, amountsDefined]) - const [inputLogoSrc, nextInputLogoSrc] = useTokenLogoSource({ - address: currencies?.[0]?.wrapped.address, - chainId, - isNative: currencies?.[0]?.isNative, - }) - const [outputLogoSrc, nextOutputLogoSrc2] = useTokenLogoSource({ - address: currencies?.[1]?.wrapped.address, - chainId, - isNative: currencies?.[1]?.isNative, - }) - if (!offchainOrderDetails || !amountsDefined) return null + const inputLogo = logos?.[0] ?? inputCurrencyInfo?.logoUrl + const outputLogo = logos?.[1] ?? outputCurrencyInfo?.logoUrl + return ( setHovered(true)} onMouseLeave={() => setHovered(false)}> - + {inputLogo && } {formatReviewSwapCurrencyAmount(amounts.inputAmount)} {amounts.inputAmount.currency.symbol} - + {outputLogo && } {formatReviewSwapCurrencyAmount(amounts.outputAmount)} {amounts.outputAmount.currency.symbol} diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitsMenu.test.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitsMenu.test.tsx index 70fbc70c202..1da5defef6e 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitsMenu.test.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/LimitsMenu.test.tsx @@ -1,3 +1,5 @@ +import 'test-utils/tokens/mocks' + import { ChainId, WETH9 } from '@uniswap/sdk-core' import { useOpenLimitOrders } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks' import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types' @@ -19,20 +21,6 @@ jest.mock('components/AccountDrawer/MiniPortfolio/formatTimestamp', () => ({ formatTimestamp: () => 'January 26, 2024 at 1:52PM', })) -jest.mock('hooks/Tokens', () => { - return { - useCurrency: (address?: string) => { - if (address?.toLowerCase() === DAI.address.toLowerCase()) { - return DAI - } - if (address?.toLowerCase() === WETH9[ChainId.MAINNET].address.toLowerCase()) { - return WETH9[ChainId.MAINNET] - } - return undefined - }, - } -}) - const mockOrderDetails: UniswapXOrderDetails = { type: SignatureType.SIGN_LIMIT, orderHash: '0x1234', diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/__snapshots__/LimitDetailActivityRow.test.tsx.snap b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/__snapshots__/LimitDetailActivityRow.test.tsx.snap index 3c863c29806..d3f174be935 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/__snapshots__/LimitDetailActivityRow.test.tsx.snap +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Limits/__snapshots__/LimitDetailActivityRow.test.tsx.snap @@ -117,7 +117,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = ` } .c14 { - border-color: #22222212; + border-color: #CECECE; display: inline-block; margin-right: 1px; border-radius: 4px; @@ -217,6 +217,7 @@ exports[`LimitDetailActivityRow should render with valid details 1`] = ` >
-
@@ -441,9 +432,6 @@ exports[`LimitsMenu should render when there are two open orders 1`] = ` points="12 5 19 12 12 19" /> -
@@ -453,7 +441,7 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
when 0.00042 WETH/DAI
@@ -461,20 +449,20 @@ exports[`LimitsMenu should render when there are two open orders 1`] = `
: Claiming} {!claimConfirmed && ( - + {{ unclaimedUni }} UNI )} diff --git a/apps/web/src/components/swap/SwapHeader.test.tsx b/apps/web/src/components/swap/SwapHeader.test.tsx index 3c6043db263..7438e17e209 100644 --- a/apps/web/src/components/swap/SwapHeader.test.tsx +++ b/apps/web/src/components/swap/SwapHeader.test.tsx @@ -3,8 +3,8 @@ import { Dispatch, PropsWithChildren, SetStateAction } from 'react' import { CurrencyState, EMPTY_DERIVED_SWAP_INFO, SwapAndLimitContext, SwapContext } from 'state/swap/types' import { mocked } from 'test-utils/mocked' import { act, render, screen } from 'test-utils/render' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import SwapHeader from './SwapHeader' import { Field, SwapTab } from './constants' diff --git a/apps/web/src/components/swap/SwapHeader.tsx b/apps/web/src/components/swap/SwapHeader.tsx index 41a51f7b440..ab14e990dcf 100644 --- a/apps/web/src/components/swap/SwapHeader.tsx +++ b/apps/web/src/components/swap/SwapHeader.tsx @@ -7,8 +7,8 @@ import { isIFramed } from 'utils/isIFramed' import { sendAnalyticsEvent } from 'analytics' import { useCallback, useEffect } from 'react' import { useLocation, useNavigate } from 'react-router-dom' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { RowBetween, RowFixed } from '../Row' import SettingsTab from '../Settings' import SwapBuyFiatButton from './SwapBuyFiatButton' diff --git a/apps/web/src/components/swap/SwapLineItem.test.tsx b/apps/web/src/components/swap/SwapLineItem.test.tsx index 74f6a5654b7..e84099840e1 100644 --- a/apps/web/src/components/swap/SwapLineItem.test.tsx +++ b/apps/web/src/components/swap/SwapLineItem.test.tsx @@ -1,3 +1,5 @@ +import 'test-utils/tokens/mocks' + import { Percent } from '@uniswap/sdk-core' import { InterfaceTrade } from 'state/routing/types' import { diff --git a/apps/web/src/components/swap/SwapPreview.test.tsx b/apps/web/src/components/swap/SwapPreview.test.tsx index 795fb6e969d..a4acaf2f94b 100644 --- a/apps/web/src/components/swap/SwapPreview.test.tsx +++ b/apps/web/src/components/swap/SwapPreview.test.tsx @@ -1,3 +1,5 @@ +import 'test-utils/tokens/mocks' + import { ETH_MAINNET, PREVIEW_EXACT_IN_TRADE, diff --git a/apps/web/src/components/swap/UnsupportedCurrencyFooter.test.tsx b/apps/web/src/components/swap/UnsupportedCurrencyFooter.test.tsx index ec29132c473..f0c40ac48a9 100644 --- a/apps/web/src/components/swap/UnsupportedCurrencyFooter.test.tsx +++ b/apps/web/src/components/swap/UnsupportedCurrencyFooter.test.tsx @@ -12,7 +12,7 @@ const unsupportedTokenSymbol = 'ALTDOM-MAR2021' const unsupportedToken = new Token(1, unsupportedTokenAddress, 18, 'ALTDOM-MAR2021') const unsupportedTokenExplorerLink = 'www.blahblah.com' -jest.mock('../../hooks/Tokens') +jest.mock('hooks/Tokens') jest.mock('../../utils/getExplorerLink') jest.setTimeout(15_000) diff --git a/apps/web/src/constants/lists.ts b/apps/web/src/constants/lists.ts index 889e1234f72..37f60f04171 100644 --- a/apps/web/src/constants/lists.ts +++ b/apps/web/src/constants/lists.ts @@ -1,5 +1,5 @@ export const UNI_LIST = 'https://cloudflare-ipfs.com/ipns/tokens.uniswap.org' -export const UNI_EXTENDED_LIST = 'https://cloudflare-ipfs.com/ipns/extendedtokens.uniswap.org' +const UNI_EXTENDED_LIST = 'https://cloudflare-ipfs.com/ipns/extendedtokens.uniswap.org' const UNI_UNSUPPORTED_LIST = 'https://cloudflare-ipfs.com/ipns/unsupportedtokens.uniswap.org' const AAVE_LIST = 'tokenlist.aave.eth' const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json' diff --git a/apps/web/src/constants/supportArticles.ts b/apps/web/src/constants/supportArticles.ts index 17c5c9c4f08..f925e302193 100644 --- a/apps/web/src/constants/supportArticles.ts +++ b/apps/web/src/constants/supportArticles.ts @@ -10,4 +10,5 @@ export enum SupportArticleURL { MOONPAY_REGIONAL_AVAILABILITY = 'https://support.uniswap.org/hc/en-us/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-', LEARN_ABOUT_LIMITS = 'https://support.uniswap.org/hc/en-us/sections/24372644881293', LIMIT_FAILURE = 'https://support.uniswap.org/hc/en-us/articles/24300813697933-Why-did-my-limit-order-fail-or-not-execute', + IMPERMANENT_LOSS = 'https://support.uniswap.org/hc/en-us/articles/20904453751693-What-is-Impermanent-Loss', } diff --git a/apps/web/src/constants/tokenLogoLookup.ts b/apps/web/src/constants/tokenLogoLookup.ts deleted file mode 100644 index 02492b1861b..00000000000 --- a/apps/web/src/constants/tokenLogoLookup.ts +++ /dev/null @@ -1,43 +0,0 @@ -import store from 'state' - -import { DEFAULT_LIST_OF_LISTS } from './lists' - -class TokenLogoLookupTable { - private dict: { [key: string]: string[] | undefined } = {} - private initialized = false - - initialize() { - const dict: { [key: string]: string[] | undefined } = {} - - DEFAULT_LIST_OF_LISTS.forEach((list) => { - const listData = store.getState().lists.byUrl[list] - if (!listData) { - return - } - listData.current?.tokens.forEach((token) => { - if (token.logoURI) { - const lowercaseAddress = token.address.toLowerCase() - const currentEntry = dict[lowercaseAddress + ':' + token.chainId] - if (currentEntry) { - currentEntry.push(token.logoURI) - } else { - dict[lowercaseAddress + ':' + token.chainId] = [token.logoURI] - } - } - }) - }) - this.dict = dict - this.initialized = true - } - getIcons(address?: string | null, chainId: number | null = 1) { - if (!address) return undefined - - if (!this.initialized) { - this.initialize() - } - - return this.dict[address.toLowerCase() + ':' + chainId] - } -} - -export default new TokenLogoLookupTable() diff --git a/apps/web/src/constants/tokenSaftey.test.ts b/apps/web/src/constants/tokenSafety.test.ts similarity index 100% rename from apps/web/src/constants/tokenSaftey.test.ts rename to apps/web/src/constants/tokenSafety.test.ts diff --git a/apps/web/src/constants/tokenSafety.tsx b/apps/web/src/constants/tokenSafety.tsx index d65d8dc1e1c..8e1c40547c4 100644 --- a/apps/web/src/constants/tokenSafety.tsx +++ b/apps/web/src/constants/tokenSafety.tsx @@ -1,27 +1,23 @@ -import { SearchToken } from 'graphql/data/SearchTokens' +import { ChainId } from '@uniswap/sdk-core' +import { useCurrencyInfo } from 'hooks/Tokens' import { Plural, Trans, t } from 'i18n' -import { TokenStandard } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { ZERO_ADDRESS } from './misc' -import tokenSafetyLookup, { TOKEN_LIST_TYPES } from './tokenSafetyLookup' -import { NATIVE_CHAIN_ID } from './tokens' +import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' export const TOKEN_SAFETY_ARTICLE = 'https://support.uniswap.org/hc/en-us/articles/8723118437133' -export enum WARNING_LEVEL { - MEDIUM, - UNKNOWN, - BLOCKED, +const SafetyLevelWeight = { + [SafetyLevel.Blocked]: 4, + [SafetyLevel.StrongWarning]: 3, + [SafetyLevel.MediumWarning]: 2, + [SafetyLevel.Verified]: 1, } /** - * Determine which warning to display based on the priority of the warnings. Prioritize blocked, than unknown, followed by the rest. Accepts two warnings passed in. + * Determine which warning to display based on the priority of the warnings. Prioritize blocked, then unknown, followed by the rest. Accepts two warnings passed in. */ export function getPriorityWarning(token0Warning: Warning | undefined, token1Warning: Warning | undefined) { if (token0Warning && token1Warning) { - if ( - token1Warning?.level === WARNING_LEVEL.BLOCKED || - (token1Warning?.level === WARNING_LEVEL.UNKNOWN && token0Warning?.level !== WARNING_LEVEL.BLOCKED) - ) { + if (SafetyLevelWeight[token1Warning.level] > SafetyLevelWeight[token0Warning.level]) { return token1Warning } return token0Warning @@ -34,7 +30,7 @@ export function getWarningCopy(warning: Warning | undefined, plural = false, tok description = null if (warning) { switch (warning.level) { - case WARNING_LEVEL.MEDIUM: + case SafetyLevel.MediumWarning: heading = ( Always conduct your own research before trading.
break - case WARNING_LEVEL.UNKNOWN: + case SafetyLevel.StrongWarning: heading = ( Always conduct your own research before trading. break - case WARNING_LEVEL.BLOCKED: + case SafetyLevel.Blocked: description = ( Caution, canProceed: true, } export const StrongWarning: Warning = { - level: WARNING_LEVEL.UNKNOWN, + level: SafetyLevel.StrongWarning, message: Warning, canProceed: true, } export const BlockedWarning: Warning = { - level: WARNING_LEVEL.BLOCKED, + level: SafetyLevel.Blocked, message: Not available, canProceed: false, } -export const NotFoundWarning: Warning = { - level: WARNING_LEVEL.UNKNOWN, - message: Token not found, - canProceed: false, -} - -export function checkWarning(tokenAddress: string, chainId?: number | null) { - if (tokenAddress === NATIVE_CHAIN_ID || tokenAddress === ZERO_ADDRESS) { - return undefined - } - switch (tokenSafetyLookup.checkToken(tokenAddress.toLowerCase(), chainId)) { - case TOKEN_LIST_TYPES.UNI_DEFAULT: - return undefined - case TOKEN_LIST_TYPES.UNI_EXTENDED: +export function useTokenWarning(tokenAddress?: string, chainId?: ChainId | number): Warning | undefined { + const currencyInfo = useCurrencyInfo(tokenAddress, chainId) + switch (currencyInfo?.safetyLevel) { + case SafetyLevel.MediumWarning: return MediumWarning - case TOKEN_LIST_TYPES.UNKNOWN: + case SafetyLevel.StrongWarning: return StrongWarning - case TOKEN_LIST_TYPES.BLOCKED: + case SafetyLevel.Blocked: return BlockedWarning - case TOKEN_LIST_TYPES.BROKEN: - return BlockedWarning - } -} - -// TODO(cartcrom): Replace all usage of WARNING_LEVEL with SafetyLevel -export function checkSearchTokenWarning(token: SearchToken) { - if (!token.address) { - return token.standard === TokenStandard.Native ? undefined : StrongWarning + default: + return undefined } - return checkWarning(token.address) } export function displayWarningLabel(warning: Warning | undefined) { - return warning && warning.level !== WARNING_LEVEL.MEDIUM + return warning && warning.level !== SafetyLevel.MediumWarning } diff --git a/apps/web/src/constants/tokenSafetyLookup.ts b/apps/web/src/constants/tokenSafetyLookup.ts deleted file mode 100644 index 20db4a13f54..00000000000 --- a/apps/web/src/constants/tokenSafetyLookup.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { TokenInfo } from '@uniswap/token-lists' - -import { ListsState } from 'state/lists/types' -import store from '../state' -import { UNI_EXTENDED_LIST, UNI_LIST, UNSUPPORTED_LIST_URLS } from './lists' -import { COMMON_BASES } from './routing' -import brokenTokenList from './tokenLists/broken.tokenlist.json' -import { NATIVE_CHAIN_ID } from './tokens' - -export enum TOKEN_LIST_TYPES { - UNI_DEFAULT = 1, - UNI_EXTENDED, - UNKNOWN, - BLOCKED, - BROKEN, -} - -class TokenSafetyLookupTable { - initialized = false - dict: { [key: string]: TOKEN_LIST_TYPES } = {} - - // TODO(WEB-2488): Index lookups by chainId - update(lists: ListsState) { - this.initialized = true - - // Initialize extended tokens first - lists.byUrl[UNI_EXTENDED_LIST]?.current?.tokens.forEach((token) => { - this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.UNI_EXTENDED - }) - - // Initialize default tokens second, so that any tokens on both default and extended will display as default (no warning) - lists.byUrl[UNI_LIST]?.current?.tokens.forEach((token) => { - this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.UNI_DEFAULT - }) - - // TODO: Figure out if this list is still relevant - brokenTokenList.tokens.forEach((token) => { - this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BROKEN - }) - - // Initialize blocked tokens from all urls included - UNSUPPORTED_LIST_URLS.map((url) => lists.byUrl[url]?.current?.tokens) - .filter((x): x is TokenInfo[] => !!x) - .flat(1) - .forEach((token) => { - this.dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BLOCKED - }) - } - - checkToken(address: string, chainId?: number | null) { - if (!this.initialized) this.update(store.getState().lists) - - if (address === NATIVE_CHAIN_ID.toLowerCase()) { - return TOKEN_LIST_TYPES.UNI_DEFAULT - } else if (chainId && COMMON_BASES[chainId]?.some((base) => address === base.wrapped.address.toLowerCase())) { - return TOKEN_LIST_TYPES.UNI_DEFAULT - } else { - return this.dict[address] ?? TOKEN_LIST_TYPES.UNKNOWN - } - } -} - -export default new TokenSafetyLookupTable() diff --git a/apps/web/src/dev/DevFlagsBox.tsx b/apps/web/src/dev/DevFlagsBox.tsx index a97e64c0688..7c0eaa86431 100644 --- a/apps/web/src/dev/DevFlagsBox.tsx +++ b/apps/web/src/dev/DevFlagsBox.tsx @@ -5,10 +5,10 @@ import { useState } from 'react' import { Flag, Settings } from 'react-feather' import { useCloseModal, useToggleModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' -import { Statsig } from 'statsig-react' import styled from 'styled-components' import { ThemedText } from 'theme/components' import { Z_INDEX } from 'theme/zIndex' +import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig' import { isDevelopmentEnv, isStagingEnv } from 'utils/env' const Box = styled.div` diff --git a/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts b/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts index 7f548c35032..55cfb6429a8 100644 --- a/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts +++ b/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts @@ -1,6 +1,6 @@ import { ChainId } from '@uniswap/sdk-core' -import { DynamicConfigs } from 'uniswap/src/features/experiments/configs' -import { useDynamicConfig } from 'uniswap/src/features/experiments/hooks' +import { DynamicConfigs } from 'uniswap/src/features/statsig/configs' +import { useDynamicConfig } from 'uniswap/src/features/statsig/hooks' export const QUICK_ROUTE_CONFIG_KEY = 'quick_route_chains' diff --git a/apps/web/src/featureFlags/flags/outageBanner.ts b/apps/web/src/featureFlags/flags/outageBanner.ts index 4e85de6a885..1b949311bcd 100644 --- a/apps/web/src/featureFlags/flags/outageBanner.ts +++ b/apps/web/src/featureFlags/flags/outageBanner.ts @@ -2,8 +2,8 @@ import { ApolloError } from '@apollo/client' import { ChainId } from '@uniswap/sdk-core' import { atomWithReset, useResetAtom, useUpdateAtom } from 'jotai/utils' import { ProtocolVersion } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' export type ChainOutageData = { chainId: ChainId diff --git a/apps/web/src/featureFlags/index.tsx b/apps/web/src/featureFlags/index.tsx index 6ac238d41c8..a2cae24ffaf 100644 --- a/apps/web/src/featureFlags/index.tsx +++ b/apps/web/src/featureFlags/index.tsx @@ -1,6 +1,6 @@ import useParsedQueryString from 'hooks/useParsedQueryString' import { useContext, useEffect } from 'react' -import { Statsig, StatsigContext } from 'statsig-react' +import { Statsig, StatsigContext } from 'uniswap/src/features/statsig/sdk/statsig' export function useFeatureFlagURLOverrides() { const parsedQs = useParsedQueryString() @@ -10,14 +10,13 @@ export function useFeatureFlagURLOverrides() { // Override on const featureFlagOverrides = typeof parsedQs.featureFlagOverride === 'string' ? parsedQs.featureFlagOverride.split(',') : [] - featureFlagOverrides.forEach((gate) => { - Statsig.overrideGate(gate, true) - }) // Override off const featureFlagOverridesOff = typeof parsedQs.featureFlagOverrideOff === 'string' ? parsedQs.featureFlagOverrideOff.split(',') : [] - featureFlagOverridesOff.forEach((gate) => { - Statsig.overrideGate(gate, false) - }) + + if (statsigContext.initialized) { + featureFlagOverrides.forEach((gate) => Statsig.overrideGate(gate, true)) + featureFlagOverridesOff.forEach((gate) => Statsig.overrideGate(gate, false)) + } }, [statsigContext.initialized, parsedQs.featureFlagOverride, parsedQs.featureFlagOverrideOff]) } diff --git a/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx b/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx index a046c94d59b..ea48a31209b 100644 --- a/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx +++ b/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx @@ -6,8 +6,8 @@ import { OnAssetActivitySubscription, useOnAssetActivitySubscription, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { v4 as uuidV4 } from 'uuid' const SubscriptionContext = createContext< diff --git a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx index c2737a8dc13..c4994f0928b 100644 --- a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx +++ b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.test.tsx @@ -3,8 +3,8 @@ import { useWeb3React } from '@web3-react/core' import { mocked } from 'test-utils/mocked' import { render, renderHook } from 'test-utils/render' import { useOnAssetActivitySubscription } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { PrefetchBalancesWrapper, useTokenBalancesQuery } from './TokenBalancesProvider' const mockLazyFetch = jest.fn() @@ -85,11 +85,7 @@ describe('ApolloProvider', () => { const { rerender } = renderHook(() => useTokenBalancesQuery()) // Balances should refetch when account changes - mocked(useOnAssetActivitySubscription).mockReturnValue({ - data: undefined, - loading: false, - variables: { account: '0xaddress2', subscriptionId: '456' }, - }) + mocked(useWeb3React).mockReturnValue({ account: '0xaddress2', chainId: 1 } as any) rerender() expect(mockLazyFetch).toHaveBeenCalledTimes(2) }) diff --git a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx index 98c5ce72ea2..ed6a587accd 100644 --- a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx +++ b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx @@ -12,8 +12,8 @@ import { // eslint-disable-next-line @typescript-eslint/no-restricted-imports usePortfolioBalancesWebLazyQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { SUBSCRIPTION_CHAINIDS } from 'utilities/src/apollo/constants' import { usePrevious } from 'utilities/src/react/hooks' import { useAssetActivitySubscription } from './AssetActivityProvider' diff --git a/apps/web/src/graphql/data/apollo/client.ts b/apps/web/src/graphql/data/apollo/client.ts index cd178b4b4df..e247e5dae3e 100644 --- a/apps/web/src/graphql/data/apollo/client.ts +++ b/apps/web/src/graphql/data/apollo/client.ts @@ -30,7 +30,7 @@ export const apolloClient = new ApolloClient({ // Tokens should be cached by their chain/address, *not* by the ID returned by the server. // This is because the ID may change depending on fields requested. read(_, { args, toReference }): Reference | undefined { - return toReference({ __typename: 'Token', chain: args?.chain, address: args?.address }) + return toReference({ __typename: 'Token', chain: args?.chain, address: args?.address?.toLowerCase() }) }, }, }, diff --git a/apps/web/src/graphql/data/types.test.ts b/apps/web/src/graphql/data/types.test.ts index 65e2f111621..3f5badd0986 100644 --- a/apps/web/src/graphql/data/types.test.ts +++ b/apps/web/src/graphql/data/types.test.ts @@ -1,5 +1,5 @@ import { ChainId } from '@uniswap/sdk-core' -import { DAI, NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' +import { DAI, NATIVE_CHAIN_ID, USDC_MAINNET, nativeOnChain } from 'constants/tokens' import { gqlTokenToCurrencyInfo } from 'graphql/data/types' import { Chain, @@ -142,4 +142,24 @@ describe('gqlTokenToCurrencyInfo', () => { safetyLevel: SafetyLevel.Verified, }) }) + + it('should return a CurrencyInfo with fields missing', () => { + const result = gqlTokenToCurrencyInfo({ + id: USDC_MAINNET.address, + address: USDC_MAINNET.address, + chain: Chain.Ethereum, + }) + expect(result).toEqual({ + currency: { + ...USDC_MAINNET, + decimals: 18, // default since it's missing + name: undefined, // default since it's missing + symbol: undefined, // default since it's missing + }, + currencyId: USDC_MAINNET.address, + isSpam: false, + logoUrl: undefined, + safetyLevel: SafetyLevel.StrongWarning, + }) + }) }) diff --git a/apps/web/src/graphql/data/types.ts b/apps/web/src/graphql/data/types.ts index a416d8c293c..faba7485608 100644 --- a/apps/web/src/graphql/data/types.ts +++ b/apps/web/src/graphql/data/types.ts @@ -6,7 +6,11 @@ import { currencyId } from 'utils/currencyId' // TODO(WEB-3839): replace all usage of Currency in the web app with CurrencyInfo // TODO: remove this function once we have it in the shared package -export function gqlTokenToCurrencyInfo(token: GqlToken): CurrencyInfo | undefined { +export function gqlTokenToCurrencyInfo(token?: GqlToken): CurrencyInfo | undefined { + if (!token) { + return undefined + } + const currency = gqlToCurrency(token) if (!currency) { @@ -16,7 +20,7 @@ export function gqlTokenToCurrencyInfo(token: GqlToken): CurrencyInfo | undefine const currencyInfo: CurrencyInfo = { currency, currencyId: currencyId(currency), - logoUrl: token.project?.logo?.url, + logoUrl: token.project?.logo?.url ?? token.project?.logoUrl, safetyLevel: token.project?.safetyLevel ?? SafetyLevel.StrongWarning, isSpam: token.project?.isSpam ?? false, } diff --git a/apps/web/src/graphql/thegraph/schema.graphql b/apps/web/src/graphql/thegraph/schema.graphql index 17fd8be6982..dd1c3c72000 100644 --- a/apps/web/src/graphql/thegraph/schema.graphql +++ b/apps/web/src/graphql/thegraph/schema.graphql @@ -5300,6 +5300,12 @@ enum TickHourData_orderBy { feesUSD } +""" +A string representation of microseconds UNIX timestamp (16 digits) + +""" +scalar Timestamp + type Token { id: ID! symbol: String! diff --git a/apps/web/src/hooks/Tokens.test.ts b/apps/web/src/hooks/Tokens.test.ts index 7861ff677e2..d2cf5fa554e 100644 --- a/apps/web/src/hooks/Tokens.test.ts +++ b/apps/web/src/hooks/Tokens.test.ts @@ -24,7 +24,7 @@ jest.mock('../state/lists/hooks.ts', () => { } }) -jest.mock('../state/hooks.ts', () => { +jest.mock('state/hooks.ts', () => { return { useAppSelector: () => ({ [MockChainId.MAINNET]: { diff --git a/apps/web/src/hooks/Tokens.ts b/apps/web/src/hooks/Tokens.ts index 8fb7496267a..cb679ed7025 100644 --- a/apps/web/src/hooks/Tokens.ts +++ b/apps/web/src/hooks/Tokens.ts @@ -24,8 +24,8 @@ import { useSimpleTokenQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { isAddress } from 'utilities/src/addresses' import { DEFAULT_ERC20_DECIMALS } from 'utilities/src/tokens/constants' import { getNativeTokenDBAddress } from 'utils/nativeTokens' @@ -344,11 +344,26 @@ export function useCurrency(address?: string, chainId?: ChainId, skip?: boolean) * be used directly if the gqlTokenListsEnabled flag is enabled, otherwise it * will return undefined every time. */ -function useCurrencyInfo(address?: string, chainId?: ChainId, skip?: boolean): Maybe { +export function useCurrencyInfo(currency?: Currency): Maybe +export function useCurrencyInfo(address?: string, chainId?: ChainId, skip?: boolean): Maybe +export function useCurrencyInfo( + addressOrCurrency?: string | Currency, + chainId?: ChainId, + skip?: boolean +): Maybe { const { chainId: connectedChainId } = useWeb3React() const gqlTokenListsEnabled = useFeatureFlag(FeatureFlags.GqlTokenLists) - const backendChainName = chainIdToBackendName(chainId ?? connectedChainId) + const address = + typeof addressOrCurrency === 'string' + ? addressOrCurrency + : addressOrCurrency?.isNative + ? NATIVE_CHAIN_ID + : addressOrCurrency?.address + + const currencyChainId = typeof addressOrCurrency === 'string' ? chainId : addressOrCurrency?.chainId + + const backendChainName = chainIdToBackendName(currencyChainId ?? connectedChainId) const isNative = address === NATIVE_CHAIN_ID || address?.toLowerCase() === 'native' || address?.toLowerCase() === 'eth' const { data } = useSimpleTokenQuery({ diff --git a/apps/web/src/hooks/useAssetLogoSource.ts b/apps/web/src/hooks/useAssetLogoSource.ts deleted file mode 100644 index 430893d122c..00000000000 --- a/apps/web/src/hooks/useAssetLogoSource.ts +++ /dev/null @@ -1,97 +0,0 @@ -import tokenLogoLookup from 'constants/tokenLogoLookup' -import { isCelo, nativeOnChain } from 'constants/tokens' -import { checkWarning, WARNING_LEVEL } from 'constants/tokenSafety' -import { chainIdToNetworkName, getNativeLogoURI } from 'lib/hooks/useCurrencyLogoURIs' -import uriToHttp from 'lib/utils/uriToHttp' -import { useCallback, useMemo, useReducer } from 'react' -import { isAddress } from 'utilities/src/addresses' - -import celoLogo from '../assets/svg/celo_logo.svg' - -const BAD_SRCS: { [tokenAddress: string]: true } = {} - -// Converts uri's into fetchable urls -function parseLogoSources(uris: string[]) { - const urls: string[] = [] - uris.forEach((uri) => urls.push(...uriToHttp(uri))) - return urls -} - -// Parses uri's, favors non-coingecko images, and improves coingecko logo quality -function prioritizeLogoSources(uris: string[]) { - const parsedUris = uris.map((uri) => uriToHttp(uri)).flat(1) - const preferredUris: string[] = [] - - // Consolidate duplicate coingecko urls into one fallback source - let coingeckoUrl: string | undefined = undefined - - parsedUris.forEach((uri) => { - if (uri.startsWith('https://assets.coingecko')) { - if (!coingeckoUrl) { - coingeckoUrl = uri.replace(/\/small\/|\/thumb\//g, '/large/') - } - } else { - preferredUris.push(uri) - } - }) - // Places coingecko urls in the back of the source array - return coingeckoUrl ? [...preferredUris, coingeckoUrl] : preferredUris -} - -export function getInitialUrl( - address?: string | null, - chainId?: number | null, - isNative?: boolean, - backupImg?: string | null -) { - if (chainId && isNative) return getNativeLogoURI(chainId) - - const networkName = chainId ? chainIdToNetworkName(chainId) : 'ethereum' - const checksummedAddress = isAddress(address) - - if (chainId && isCelo(chainId) && address === nativeOnChain(chainId).wrapped.address) { - return celoLogo - } - - if (checksummedAddress) { - return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${checksummedAddress}/logo.png` - } else { - return backupImg ?? undefined - } -} - -export default function useAssetLogoSource({ - address, - chainId, - isNative, - primaryImg, -}: { - address?: string | null - chainId?: number | null - isNative?: boolean - primaryImg?: string | null -}): [string | undefined, () => void] { - const hideLogo = Boolean(address && checkWarning(address, chainId)?.level === WARNING_LEVEL.BLOCKED) - const [srcIndex, incrementSrcIndex] = useReducer((n: number) => n + 1, 0) - - const current = useMemo(() => { - if (hideLogo) return undefined - - if (primaryImg && !BAD_SRCS[primaryImg] && !isNative) return primaryImg - - const initialUrl = getInitialUrl(address, chainId, isNative) - if (initialUrl && !BAD_SRCS[initialUrl]) return initialUrl - - const uris = tokenLogoLookup.getIcons(address, chainId) ?? [] - const fallbackSrcs = prioritizeLogoSources(parseLogoSources(uris)) - return fallbackSrcs.find((src) => !BAD_SRCS[src]) - // eslint-disable-next-line react-hooks/exhaustive-deps -- rerun when src index changes, denoting a bad src was marked - }, [address, chainId, hideLogo, isNative, primaryImg, srcIndex]) - - const nextSrc = useCallback(() => { - if (current) BAD_SRCS[current] = true - incrementSrcIndex() - }, [current]) - - return [current, nextSrc] -} diff --git a/apps/web/src/hooks/useColor.ts b/apps/web/src/hooks/useColor.ts index 7042f4f169d..90f49127dc3 100644 --- a/apps/web/src/hooks/useColor.ts +++ b/apps/web/src/hooks/useColor.ts @@ -1,5 +1,5 @@ import { Currency } from '@uniswap/sdk-core' -import useTokenLogoSource from 'hooks/useAssetLogoSource' +import { useCurrencyInfo } from 'hooks/Tokens' import { useMemo } from 'react' import { useTheme } from 'styled-components' import { useExtractedTokenColor } from 'ui/src/utils/colors' @@ -8,11 +8,8 @@ type ContrastSettings = { backgroundColor: string; darkMode: boolean } export function useColor(currency?: Currency, contrastSettings?: ContrastSettings) { const theme = useTheme() - const [src] = useTokenLogoSource({ - address: currency?.wrapped.address, - chainId: currency?.chainId, - isNative: currency?.isNative, - }) + const currencyInfo = useCurrencyInfo(currency) + const src = currencyInfo?.logoUrl ?? undefined return useSrcColor(src, currency?.name, contrastSettings?.backgroundColor).tokenColor ?? theme.accent1 } diff --git a/apps/web/src/hooks/useENSAvatar.ts b/apps/web/src/hooks/useENSAvatar.ts index 2939cfd9642..b57e592e254 100644 --- a/apps/web/src/hooks/useENSAvatar.ts +++ b/apps/web/src/hooks/useENSAvatar.ts @@ -31,7 +31,7 @@ export default function useENSAvatar( const nameAvatar = useAvatarFromNode(ENSName === null ? undefined : safeNamehash(ENSName)) let avatar = addressAvatar.avatar || nameAvatar.avatar - const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership) + const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership, address) avatar = nftAvatar.avatar || avatar const http = avatar && uriToHttp(avatar)[0] @@ -66,7 +66,11 @@ function useAvatarFromNode(node?: string): { avatar?: string; loading: boolean } ) } -function useAvatarFromNFT(nftUri = '', enforceOwnership: boolean): { avatar?: string; loading: boolean } { +function useAvatarFromNFT( + nftUri = '', + enforceOwnership: boolean, + address?: string +): { avatar?: string; loading: boolean } { const parts = nftUri.toLowerCase().split(':') const protocol = parts[0] // ignore the chain from eip155 @@ -76,7 +80,12 @@ function useAvatarFromNFT(nftUri = '', enforceOwnership: boolean): { avatar?: st const isERC721 = protocol === 'eip155' && erc === 'erc721' const isERC1155 = protocol === 'eip155' && erc === 'erc1155' const erc721 = useERC721Uri(isERC721 ? contractAddress : undefined, isERC721 ? id : undefined, enforceOwnership) - const erc1155 = useERC1155Uri(isERC1155 ? contractAddress : undefined, isERC1155 ? id : undefined, enforceOwnership) + const erc1155 = useERC1155Uri( + isERC1155 ? contractAddress : undefined, + address, + isERC1155 ? id : undefined, + enforceOwnership + ) const uri = erc721.uri || erc1155.uri const http = uri && uriToHttp(uri)[0] @@ -125,12 +134,12 @@ function useERC721Uri( function useERC1155Uri( contractAddress: string | undefined, + ownerAddress: string | undefined, id: string | undefined, enforceOwnership: boolean ): { uri?: string; loading: boolean } { - const { account } = useWeb3React() const idArgument = useMemo(() => [id], [id]) - const accountArgument = useMemo(() => [account || '', id], [account, id]) + const accountArgument = useMemo(() => [ownerAddress, id], [ownerAddress, id]) const contract = useERC1155Contract(contractAddress) const balance = useMainnetSingleCallResult(contract, 'balanceOf', accountArgument, NEVER_RELOAD) const uri = useMainnetSingleCallResult(contract, 'uri', idArgument, NEVER_RELOAD) diff --git a/apps/web/src/hooks/useEthersProvider.ts b/apps/web/src/hooks/useEthersProvider.ts new file mode 100644 index 00000000000..5bba274f557 --- /dev/null +++ b/apps/web/src/hooks/useEthersProvider.ts @@ -0,0 +1,29 @@ +import { FallbackProvider, StaticJsonRpcProvider, Web3Provider } from '@ethersproject/providers' +import { useMemo } from 'react' +import type { Chain, Client, Transport } from 'viem' +import { Config, useClient } from 'wagmi' + +function clientToProvider(client?: Client) { + if (!client) return undefined + const { chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') + return new FallbackProvider( + (transport.transports as ReturnType[]).map( + ({ value }) => new StaticJsonRpcProvider(value?.url, network) + ) + ) + return new Web3Provider(transport, network) +} + +/** Hook to convert a viem Client to an ethers.js Provider. */ +// TODO(wagmi migration): Remove eslinst disable when hook is used +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function useEthersProvider({ chainId }: { chainId?: number } = {}) { + const client = useClient({ chainId }) + return useMemo(() => clientToProvider(client), [client]) +} diff --git a/apps/web/src/hooks/useEthersSigner.ts b/apps/web/src/hooks/useEthersSigner.ts new file mode 100644 index 00000000000..bcd2ec2b801 --- /dev/null +++ b/apps/web/src/hooks/useEthersSigner.ts @@ -0,0 +1,25 @@ +import { Web3Provider } from '@ethersproject/providers' +import { useMemo } from 'react' +import type { Account, Chain, Client, Transport } from 'viem' +import { Config, useConnectorClient } from 'wagmi' + +function clientToSigner(client?: Client) { + if (!client) return undefined + const { account, chain, transport } = client + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + const provider = new Web3Provider(transport, network) + const signer = provider.getSigner(account.address) + return signer +} + +/** Hook to convert a Viem Client to an ethers.js Signer. */ +// TODO(wagmi migration): Remove eslinst disable when hook is used +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function useEthersSigner({ chainId }: { chainId?: number } = {}) { + const { data: client } = useConnectorClient({ chainId }) + return useMemo(() => clientToSigner(client), [client]) +} diff --git a/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.test.ts b/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.test.ts index 0ea64c8df1a..8f9aeb580e0 100644 --- a/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.test.ts +++ b/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.test.ts @@ -3,41 +3,56 @@ import { CallState } from '@uniswap/redux-multicall' import { renderHook } from 'test-utils/render' import { PositionDetails } from 'types/position' +import { ChainId, Token, WETH9 } from '@uniswap/sdk-core' +import { DAI } from 'constants/tokens' +import { useDefaultActiveTokens } from 'hooks/Tokens' +import { mocked } from 'test-utils/mocked' import { useFilterPossiblyMaliciousPositions } from './useFilterPossiblyMaliciousPositions' import { useTokenContractsConstant } from './useTokenContractsConstant' jest.mock('./useTokenContractsConstant') -jest.mock('./Tokens', () => { - return { - useDefaultActiveTokens: () => ({ - '0x4200000000000000000000000000000000000006': { - chainId: 10, - address: '0x4200000000000000000000000000000000000006', - name: 'Wrapped Ether', - symbol: 'WETH', - decimals: 18, - logoURI: 'https://ethereum-optimism.github.io/data/WETH/logo.png', - extensions: {}, - }, - '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1': { - chainId: 10, - address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', - name: 'Dai Stablecoin', - symbol: 'DAI', - decimals: 18, - logoURI: 'https://ethereum-optimism.github.io/data/DAI/logo.svg', - extensions: { - optimismBridgeAddress: '0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65', - }, - }, - }), - } -}) +jest.mock('hooks/Tokens') const mockUseTokenContractsConstant = useTokenContractsConstant as jest.MockedFunction beforeEach(() => { mockUseTokenContractsConstant.mockReturnValue([]) + mocked(useDefaultActiveTokens).mockImplementation(() => ({ + '0x4200000000000000000000000000000000000006': { + chainId: 10, + address: '0x4200000000000000000000000000000000000006', + name: 'Wrapped Ether', + symbol: 'WETH', + decimals: 18, + logoURI: 'https://ethereum-optimism.github.io/data/WETH/logo.png', + extensions: {}, + isNative: false, + isToken: true, + equals(other) { + return !other.isNative && other?.address?.toLowerCase() === this.address.toLowerCase() + }, + wrapped: WETH9[ChainId.MAINNET], + sortsBefore: (other) => other.address < WETH9[ChainId.MAINNET].address, + } as Token, + '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1': { + chainId: 10, + address: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', + name: 'Dai Stablecoin', + symbol: 'DAI', + decimals: 18, + logoURI: 'https://ethereum-optimism.github.io/data/DAI/logo.svg', + extensions: { + optimismBridgeAddress: '0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65', + }, + isNative: false, + isToken: true, + equals(other) { + return !other.isNative && other?.address?.toLowerCase() === this.address.toLowerCase() + }, + wrapped: DAI as Token, + sortsBefore: (other) => other.address < DAI.address, + } as Token, + })) }) const positions: PositionDetails[] = [ { diff --git a/apps/web/src/hooks/useNetworkSupportsV2.ts b/apps/web/src/hooks/useNetworkSupportsV2.ts index d400896e900..7fd61171c32 100644 --- a/apps/web/src/hooks/useNetworkSupportsV2.ts +++ b/apps/web/src/hooks/useNetworkSupportsV2.ts @@ -1,7 +1,7 @@ import { useWeb3React } from '@web3-react/core' import { SUPPORTED_V2POOL_CHAIN_IDS, SUPPORTED_V2POOL_CHAIN_IDS_DEPRECATED } from 'constants/chains' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' export function useNetworkSupportsV2() { const { chainId } = useWeb3React() diff --git a/apps/web/src/hooks/useTokenWarningColor.test.ts b/apps/web/src/hooks/useTokenWarningColor.test.ts index da031227eed..4c7a85182d8 100644 --- a/apps/web/src/hooks/useTokenWarningColor.test.ts +++ b/apps/web/src/hooks/useTokenWarningColor.test.ts @@ -1,41 +1,41 @@ -import { WARNING_LEVEL } from 'constants/tokenSafety' import { renderHook } from 'test-utils/render' import { lightTheme } from 'theme/colors' import { lightDeprecatedTheme } from 'theme/deprecatedColors' +import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useTokenWarningColor, useTokenWarningTextColor } from './useTokenWarningColor' describe('Token Warning Colors', () => { describe('useTokenWarningColor', () => { it('medium', () => { - const { result } = renderHook(() => useTokenWarningColor(WARNING_LEVEL.MEDIUM)) + const { result } = renderHook(() => useTokenWarningColor(SafetyLevel.MediumWarning)) expect(result.current).toEqual(lightTheme.surface3) }) it('strong', () => { - const { result } = renderHook(() => useTokenWarningColor(WARNING_LEVEL.UNKNOWN)) + const { result } = renderHook(() => useTokenWarningColor(SafetyLevel.StrongWarning)) expect(result.current).toEqual(lightDeprecatedTheme.deprecated_accentFailureSoft) }) it('blocked', () => { - const { result } = renderHook(() => useTokenWarningColor(WARNING_LEVEL.BLOCKED)) + const { result } = renderHook(() => useTokenWarningColor(SafetyLevel.Blocked)) expect(result.current).toEqual(lightTheme.surface3) }) }) describe('useTokenWarningTextColor', () => { it('medium', () => { - const { result } = renderHook(() => useTokenWarningTextColor(WARNING_LEVEL.MEDIUM)) + const { result } = renderHook(() => useTokenWarningTextColor(SafetyLevel.MediumWarning)) expect(result.current).toEqual(lightDeprecatedTheme.deprecated_accentWarning) }) it('strong', () => { - const { result } = renderHook(() => useTokenWarningTextColor(WARNING_LEVEL.UNKNOWN)) + const { result } = renderHook(() => useTokenWarningTextColor(SafetyLevel.StrongWarning)) expect(result.current).toEqual(lightTheme.critical) }) it('blocked', () => { - const { result } = renderHook(() => useTokenWarningTextColor(WARNING_LEVEL.BLOCKED)) + const { result } = renderHook(() => useTokenWarningTextColor(SafetyLevel.Blocked)) expect(result.current).toEqual(lightTheme.neutral2) }) }) diff --git a/apps/web/src/hooks/useTokenWarningColor.ts b/apps/web/src/hooks/useTokenWarningColor.ts index 6062d4b16bf..47ffa0af4c3 100644 --- a/apps/web/src/hooks/useTokenWarningColor.ts +++ b/apps/web/src/hooks/useTokenWarningColor.ts @@ -1,27 +1,31 @@ -import { WARNING_LEVEL } from 'constants/tokenSafety' import { useTheme } from 'styled-components' +import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -export const useTokenWarningTextColor = (level: WARNING_LEVEL) => { +export const useTokenWarningTextColor = (level: SafetyLevel) => { const theme = useTheme() switch (level) { - case WARNING_LEVEL.MEDIUM: + case SafetyLevel.MediumWarning: return theme.deprecated_accentWarning - case WARNING_LEVEL.UNKNOWN: + case SafetyLevel.StrongWarning: return theme.critical - case WARNING_LEVEL.BLOCKED: + case SafetyLevel.Blocked: return theme.neutral2 + default: + return 'inherit' } } -export const useTokenWarningColor = (level: WARNING_LEVEL) => { +export const useTokenWarningColor = (level: SafetyLevel) => { const theme = useTheme() switch (level) { - case WARNING_LEVEL.MEDIUM: - case WARNING_LEVEL.BLOCKED: + case SafetyLevel.MediumWarning: + case SafetyLevel.Blocked: return theme.surface3 - case WARNING_LEVEL.UNKNOWN: + case SafetyLevel.StrongWarning: return theme.deprecated_accentFailureSoft + default: + return 'inherit' } } diff --git a/apps/web/src/hooks/useUSDPrice.ts b/apps/web/src/hooks/useUSDPrice.ts index 7025422f448..f0fd398750d 100644 --- a/apps/web/src/hooks/useUSDPrice.ts +++ b/apps/web/src/hooks/useUSDPrice.ts @@ -6,8 +6,8 @@ import { useMemo } from 'react' import { ClassicTrade, INTERNAL_ROUTER_PREFERENCE_PRICE, TradeState } from 'state/routing/types' import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { Chain, useTokenSpotPriceQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { getNativeTokenDBAddress } from 'utils/nativeTokens' +import { getNativeTokenDBAddress } from 'utils/nativeTokens' import useIsWindowVisible from './useIsWindowVisible' import useStablecoinPrice from './useStablecoinPrice' diff --git a/apps/web/src/hooks/useUniswapWalletOptions.ts b/apps/web/src/hooks/useUniswapWalletOptions.ts index 9ef3c0d58fe..d5026cec094 100644 --- a/apps/web/src/hooks/useUniswapWalletOptions.ts +++ b/apps/web/src/hooks/useUniswapWalletOptions.ts @@ -1,7 +1,7 @@ import { useEIP6963Connections } from 'components/WalletModal/useOrderedConnections' import { Connection } from 'connection/types' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' export function useUniswapWalletOptions() { const isBetaLive = useFeatureFlag(FeatureFlags.ExtensionBetaLaunch) diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index f819a3961fe..9e9c33f11db 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -25,7 +25,7 @@ import { ActivityStateUpdater } from 'state/activity/updater' import { StatsigProvider as BaseStatsigProvider, StatsigUser } from 'statsig-react' import { SystemThemeUpdater, ThemeColorMetaUpdater } from 'theme/components/ThemeToggle' import { TamaguiProvider } from 'theme/tamaguiProvider' -import { STATSIG_DUMMY_KEY } from 'tracing' +import { DUMMY_STATSIG_SDK_KEY } from 'uniswap/src/features/statsig/constants' import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context' import { getEnvName, isBrowserRouterEnabled } from 'utils/env' import { unregister as unregisterServiceWorker } from 'utils/serviceWorker' @@ -85,7 +85,7 @@ function StatsigProvider({ children }: PropsWithChildren) { return ( { @@ -87,7 +84,6 @@ describe('BagFooter.tsx', () => { allowedSlippage: new Percent(10, 100), }) mocked(usePayWithAnyTokenSwap).mockReturnValue() - mocked(useCurrency).mockReturnValue(nativeOnChain(ChainId.MAINNET)) mocked(useTokenInput).mockReturnValue({ inputCurrency: undefined, setInputCurrency: () => undefined, diff --git a/apps/web/src/nft/components/layout/Checkbox.tsx b/apps/web/src/nft/components/layout/Checkbox.tsx index 6a8ef34978b..daeeb395402 100644 --- a/apps/web/src/nft/components/layout/Checkbox.tsx +++ b/apps/web/src/nft/components/layout/Checkbox.tsx @@ -12,7 +12,7 @@ const CheckboxLabel = styled.label` line-height: 1; ` const CheckContainer = styled.span<{ checked?: boolean; hovered?: boolean; size?: number }>` - border-color: ${({ checked, hovered, theme }) => (checked || hovered ? theme.accent1 : theme.surface3)}; + border-color: ${({ checked, hovered, theme }) => (checked || hovered ? theme.accent1 : theme.neutral3)}; background: ${({ checked, theme }) => (checked ? theme.accent1 : undefined)}; display: inline-block; margin-right: 1px; diff --git a/apps/web/src/nft/pages/collection/index.tsx b/apps/web/src/nft/pages/collection/index.tsx index b2e606c5fed..36b9e0e58d9 100644 --- a/apps/web/src/nft/pages/collection/index.tsx +++ b/apps/web/src/nft/pages/collection/index.tsx @@ -102,7 +102,7 @@ const MobileFilterHeader = styled(Row)` justify-content: space-between; ` -// Sticky navbar on light mode looks incorrect because the box shadows from assets overlap the the edges of the navbar. +// Sticky navbar on light mode looks incorrect because the box shadows from assets overlap the edges of the navbar. // As a result it needs 16px padding on either side. These paddings are offset by 16px to account for this. Please see CollectionNFTs.css.ts for the additional sizing context. // See breakpoint values in ScreenBreakpointsPaddings above - they must match const CollectionDisplaySection = styled(Row)` diff --git a/apps/web/src/pages/App.tsx b/apps/web/src/pages/App.tsx index 8d56f891f12..ac063a7cef6 100644 --- a/apps/web/src/pages/App.tsx +++ b/apps/web/src/pages/App.tsx @@ -23,7 +23,6 @@ import { isPathBlocked } from 'utils/blockedPaths' import { MICROSITE_LINK } from 'utils/openDownloadApp' import { getCurrentPageFromLocation } from 'utils/urlRoutes' import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals' - import { findRouteByPath, RouteDefinition, routes, useRouterConfig } from './RouteDefinitions' // The Chrome is always loaded, but is lazy-loaded because it is not needed without user interaction. diff --git a/apps/web/src/pages/Landing/index.tsx b/apps/web/src/pages/Landing/index.tsx index c75146fdfdb..37ea3ba3ddf 100644 --- a/apps/web/src/pages/Landing/index.tsx +++ b/apps/web/src/pages/Landing/index.tsx @@ -10,8 +10,8 @@ import { Navigate, useLocation, useNavigate } from 'react-router-dom' import { useAppDispatch } from 'state/hooks' import { setRecentConnectionDisconnected } from 'state/user/reducer' import { TRANSITION_DURATIONS } from 'theme/styles' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import LandingV2 from './LandingV2' export default function Landing() { diff --git a/apps/web/src/pages/Pool/PositionPage.test.tsx b/apps/web/src/pages/Pool/PositionPage.test.tsx index 1c4e97013e5..a9bd17f8b5b 100644 --- a/apps/web/src/pages/Pool/PositionPage.test.tsx +++ b/apps/web/src/pages/Pool/PositionPage.test.tsx @@ -1,8 +1,9 @@ +import 'test-utils/tokens/mocks' + import { BigNumber } from '@ethersproject/bignumber' import { CurrencyAmount, WETH9 } from '@uniswap/sdk-core' import { FeeAmount, Pool } from '@uniswap/v3-sdk' import { USDC_MAINNET } from 'constants/tokens' -import { useToken } from 'hooks/Tokens' import { PoolState, usePool } from 'hooks/usePools' import { useV3PositionFees } from 'hooks/useV3PositionFees' import * as useV3Positions from 'hooks/useV3Positions' @@ -13,7 +14,6 @@ import { useFormatter } from 'utils/formatNumbers' import PositionPage from './PositionPage' -jest.mock('hooks/Tokens') jest.mock('utils/unwrappedToken') jest.mock('hooks/useV3Positions') jest.mock('hooks/useV3PositionFees') @@ -52,15 +52,6 @@ describe('position page', () => { mocked(useV3Positions.useV3PositionFromTokenId).mockImplementation(() => { return { loading: false, position: positionDetails } }) - mocked(useToken).mockImplementation((tokenAddress?: string | undefined) => { - if (!tokenAddress) return undefined - - if (tokenAddress === USDC_MAINNET.address) { - return USDC_MAINNET - } else { - return WETH9[1] - } - }) mocked(usePool).mockImplementation(() => { return [PoolState.EXISTS, pool] }) diff --git a/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.test.tsx b/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.test.tsx index 748f43b4cb2..91fdab714ec 100644 --- a/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.test.tsx +++ b/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.test.tsx @@ -3,8 +3,8 @@ import { NewAddressSpeedBumpModal } from 'pages/Swap/Send/NewAddressSpeedBump' import { SendContext, SendContextType } from 'state/send/SendContext' import { mocked } from 'test-utils/mocked' import { render, screen } from 'test-utils/render' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' const mockSendContext: SendContextType = { sendState: { @@ -25,7 +25,7 @@ const mockSendContext: SendContextType = { setSendState: jest.fn(), } -jest.mock('uniswap/src/features/experiments/hooks', () => { +jest.mock('uniswap/src/features/statsig/hooks', () => { return { useFeatureFlag: jest.fn(), } diff --git a/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.tsx b/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.tsx index 3ff04908839..059be0da81c 100644 --- a/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.tsx +++ b/apps/web/src/pages/Swap/Send/NewAddressSpeedBump.tsx @@ -8,8 +8,8 @@ import { useSendContext } from 'state/send/SendContext' import styled, { useTheme } from 'styled-components' import { ThemedText } from 'theme/components' import { UniconV2 } from 'ui/src/components/UniconV2' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' const StyledUserIcon = styled(UserIcon)` width: 28px; diff --git a/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.test.tsx b/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.test.tsx index 66d2697491f..0ef3fd1c816 100644 --- a/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.test.tsx +++ b/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.test.tsx @@ -1,7 +1,12 @@ +jest.mock('hooks/Tokens') + import { SwapTab } from 'components/swap/constants' import { DAI } from 'constants/tokens' +import { useCurrencyInfo } from 'hooks/Tokens' import { SendContext, SendContextType } from 'state/send/SendContext' import { SwapAndLimitContext } from 'state/swap/types' +import { DAI_INFO } from 'test-utils/constants' +import { mocked } from 'test-utils/mocked' import { render, screen, waitFor } from 'test-utils/render' import SendCurrencyInputForm from './SendCurrencyInputForm' @@ -57,6 +62,12 @@ const mockedSendContextTokenInput: SendContextType = { } describe('SendCurrencyInputform', () => { + beforeEach(() => { + mocked(useCurrencyInfo).mockImplementation(() => { + return DAI_INFO + }) + }) + it('should render placeholder values', async () => { const { container } = render( diff --git a/apps/web/src/pages/Swap/Send/SendRecipientForm.tsx b/apps/web/src/pages/Swap/Send/SendRecipientForm.tsx index 63074887459..98ab08e0dfc 100644 --- a/apps/web/src/pages/Swap/Send/SendRecipientForm.tsx +++ b/apps/web/src/pages/Swap/Send/SendRecipientForm.tsx @@ -2,6 +2,7 @@ import { useWeb3React } from '@web3-react/core' import Column, { AutoColumn } from 'components/Column' import Identicon from 'components/Identicon' import Row from 'components/Row' +import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' import { Unicon } from 'components/Unicon' import { UniTagProfilePicture } from 'components/UniTag/UniTagProfilePicture' import useENSName from 'hooks/useENSName' @@ -19,8 +20,8 @@ import { AnimationType } from 'theme/components/FadePresence' import { Unitag } from 'ui/src/components/icons/Unitag' import { Text } from 'ui/src/components/text/Text' import { UniconV2 } from 'ui/src/components/UniconV2' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { useUnitagByAddressWithoutFlag, useUnitagByNameWithoutFlag, @@ -130,6 +131,7 @@ const AutocompleteRow = ({ const cachedEnsName = ENSName || validatedEnsName const formattedAddress = shortenAddress(address) const uniconsV2Enabled = useFeatureFlag(FeatureFlags.UniconsV2) + const shouldShowAddress = !unitag?.username && !cachedEnsName const boundSelectRecipient = useCallback( () => @@ -155,13 +157,21 @@ const AutocompleteRow = ({ )} - - {unitag?.username ?? cachedEnsName ?? formattedAddress} - + {shouldShowAddress ? ( + + {formattedAddress} + + ) : ( + {unitag?.username ?? cachedEnsName} + )} {unitag?.username && } - {(unitag || cachedEnsName) && ( - {formattedAddress} + {!shouldShowAddress && ( + + + {formattedAddress} + + )} diff --git a/apps/web/src/pages/Swap/Send/SendReviewModal.test.tsx b/apps/web/src/pages/Swap/Send/SendReviewModal.test.tsx index aa03c2279b8..7886b80de25 100644 --- a/apps/web/src/pages/Swap/Send/SendReviewModal.test.tsx +++ b/apps/web/src/pages/Swap/Send/SendReviewModal.test.tsx @@ -1,3 +1,5 @@ +import 'test-utils/tokens/mocks' + import { SwapTab } from 'components/swap/constants' import { DAI } from 'constants/tokens' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' diff --git a/apps/web/src/pages/Swap/Send/SendReviewModal.tsx b/apps/web/src/pages/Swap/Send/SendReviewModal.tsx index fd9bed123c7..67b7871f066 100644 --- a/apps/web/src/pages/Swap/Send/SendReviewModal.tsx +++ b/apps/web/src/pages/Swap/Send/SendReviewModal.tsx @@ -18,8 +18,8 @@ import styled from 'styled-components' import { ClickableStyle, CloseIcon, Separator, ThemedText } from 'theme/components' import { UniconV2 } from 'ui/src/components/UniconV2' import { Unitag } from 'ui/src/components/icons/Unitag' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { useUnitagByNameWithoutFlag } from 'uniswap/src/features/unitags/hooksWithoutFlags' import { shortenAddress } from 'utilities/src/addresses' import { NumberType, useFormatter } from 'utils/formatNumbers' diff --git a/apps/web/src/pages/TokenDetails/index.tsx b/apps/web/src/pages/TokenDetails/index.tsx index 72e9d5eda15..d88616ad6ae 100644 --- a/apps/web/src/pages/TokenDetails/index.tsx +++ b/apps/web/src/pages/TokenDetails/index.tsx @@ -4,7 +4,7 @@ import TokenDetails from 'components/Tokens/TokenDetails' import { useCreateTDPChartState } from 'components/Tokens/TokenDetails/ChartSection' import InvalidTokenDetails from 'components/Tokens/TokenDetails/InvalidTokenDetails' import { TokenDetailsPageSkeleton } from 'components/Tokens/TokenDetails/Skeleton' -import { checkWarning } from 'constants/tokenSafety' +import { useTokenWarning } from 'constants/tokenSafety' import { NATIVE_CHAIN_ID, UNKNOWN_TOKEN_SYMBOL, nativeOnChain } from 'constants/tokens' import { useTokenBalancesQuery } from 'graphql/data/apollo/TokenBalancesProvider' import { gqlToCurrency, supportedChainIdFromGQLChain, validateUrlChainParam } from 'graphql/data/util' @@ -96,7 +96,7 @@ function useCreateTDPContext(): PendingTDPContext | LoadedTDPContext { const { currency, currencyWasFetchedOnChain } = useTDPCurrency(tokenQuery, tokenAddress, currencyChainId, isNative) - const warning = checkWarning(tokenAddress, currencyChainId) + const warning = useTokenWarning(tokenAddress, currencyChainId) // Extract color for page usage const theme = useTheme() diff --git a/apps/web/src/setupTests.ts b/apps/web/src/setupTests.ts index e3f2536f6c5..f6ab13ee666 100644 --- a/apps/web/src/setupTests.ts +++ b/apps/web/src/setupTests.ts @@ -11,7 +11,7 @@ import React from 'react' import { Readable } from 'stream' import { toBeVisible } from 'test-utils/matchers' import { mocked } from 'test-utils/mocked' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { TextDecoder, TextEncoder } from 'util' // Sets origin to the production origin, because some tests depend on this. @@ -119,7 +119,7 @@ jest.mock('state/routing/quickRouteSlice', () => { } }) -jest.mock('uniswap/src/features/experiments/hooks') +jest.mock('uniswap/src/features/statsig/hooks') // Mocks are configured to reset between tests (by CRA), so they must be set in a beforeEach. beforeEach(() => { diff --git a/apps/web/src/state/activity/orders.ts b/apps/web/src/state/activity/polling/orders.ts similarity index 90% rename from apps/web/src/state/activity/orders.ts rename to apps/web/src/state/activity/polling/orders.ts index 9354f710ce6..4866efeb021 100644 --- a/apps/web/src/state/activity/orders.ts +++ b/apps/web/src/state/activity/polling/orders.ts @@ -2,13 +2,13 @@ import { TradeType } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import ms from 'ms' import { useEffect, useState } from 'react' -import { ActivityUpdaterFn } from 'state/activity/updater' import { isFinalizedOrder, usePendingOrders } from 'state/signatures/hooks' import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types' import { OrderQueryResponse, UniswapXBackendOrder, UniswapXOrderStatus } from 'types/uniswapx' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' -import { toSerializableReceipt } from './transactions' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' +import { OnActivityUpdate } from '../types' +import { toSerializableReceipt } from '../utils' const UNISWAP_GATEWAY_DNS_URL = process.env.REACT_APP_UNISWAP_GATEWAY_DNS if (UNISWAP_GATEWAY_DNS_URL === undefined) { @@ -48,7 +48,7 @@ async function fetchOrderStatuses(account: string, orders: UniswapXOrderDetails[ const OFF_CHAIN_ORDER_STATUS_POLLING_INITIAL_INTERVAL = ms(`2s`) -export function usePollPendingOrders(onReceiveUpdate: ActivityUpdaterFn) { +export function usePollPendingOrders(onActivityUpdate: OnActivityUpdate) { const realtimeEnabled = useFeatureFlag(FeatureFlags.Realtime) const { account, provider } = useWeb3React() @@ -92,7 +92,7 @@ export function usePollPendingOrders(onReceiveUpdate: ActivityUpdaterFn) { receipt = toSerializableReceipt(await provider?.getTransactionReceipt(updatedOrder.txHash)) } } - onReceiveUpdate({ + onActivityUpdate({ type: 'signature', updatedStatus: updatedOrder.orderStatus, originalSignature: pendingOrder, @@ -113,7 +113,7 @@ export function usePollPendingOrders(onReceiveUpdate: ActivityUpdaterFn) { return () => clearTimeout(timeout) } return - }, [account, currentDelay, onReceiveUpdate, pendingOrders, provider, realtimeEnabled]) + }, [account, currentDelay, onActivityUpdate, pendingOrders, provider, realtimeEnabled]) return null } diff --git a/apps/web/src/state/activity/retry.test.ts b/apps/web/src/state/activity/polling/retry.test.ts similarity index 100% rename from apps/web/src/state/activity/retry.test.ts rename to apps/web/src/state/activity/polling/retry.test.ts diff --git a/apps/web/src/state/activity/retry.ts b/apps/web/src/state/activity/polling/retry.ts similarity index 100% rename from apps/web/src/state/activity/retry.ts rename to apps/web/src/state/activity/polling/retry.ts diff --git a/apps/web/src/state/activity/transactions.test.ts b/apps/web/src/state/activity/polling/transactions.test.ts similarity index 100% rename from apps/web/src/state/activity/transactions.test.ts rename to apps/web/src/state/activity/polling/transactions.test.ts diff --git a/apps/web/src/state/activity/transactions.ts b/apps/web/src/state/activity/polling/transactions.ts similarity index 77% rename from apps/web/src/state/activity/transactions.ts rename to apps/web/src/state/activity/polling/transactions.ts index c655a2c7b04..b11302ea1fc 100644 --- a/apps/web/src/state/activity/transactions.ts +++ b/apps/web/src/state/activity/polling/transactions.ts @@ -1,4 +1,3 @@ -import { TransactionReceipt } from '@ethersproject/abstract-provider' import { NEVER_RELOAD } from '@uniswap/redux-multicall' import { ChainId } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' @@ -6,14 +5,14 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' import useBlockNumber, { useFastForwardBlockNumber } from 'lib/hooks/useBlockNumber' import ms from 'ms' import { useCallback, useEffect, useMemo } from 'react' -import { ActivityUpdaterFn } from 'state/activity/updater' import { useAppDispatch } from 'state/hooks' import { isPendingTx, useMultichainTransactions, useTransactionRemover } from 'state/transactions/hooks' import { checkedTransaction } from 'state/transactions/reducer' -import { SerializableTransactionReceipt, TransactionDetails } from 'state/transactions/types' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { TransactionDetails } from 'state/transactions/types' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { SUBSCRIPTION_CHAINIDS } from 'utilities/src/apollo/constants' +import { OnActivityUpdate } from '../types' import { CanceledError, RetryOptions, RetryableError, retry } from './retry' interface Transaction { @@ -22,19 +21,6 @@ interface Transaction { lastCheckedBlockNumber?: number } -export function toSerializableReceipt(receipt: TransactionReceipt): SerializableTransactionReceipt { - return { - blockHash: receipt.blockHash, - blockNumber: receipt.blockNumber, - contractAddress: receipt.contractAddress, - from: receipt.from, - status: receipt.status, - to: receipt.to, - transactionHash: receipt.transactionHash, - transactionIndex: receipt.transactionIndex, - } -} - export function shouldCheck(lastBlockNumber: number, tx: Transaction): boolean { if (tx.receipt) return false if (!tx.lastCheckedBlockNumber) return true @@ -63,27 +49,32 @@ const RETRY_OPTIONS_BY_CHAIN_ID: { [chainId: number]: RetryOptions } = { } const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 1, minWait: 0, maxWait: 0 } -export function usePollPendingTransactions(onReceiveUpdate: ActivityUpdaterFn) { - const realtimeEnabled = useFeatureFlag(FeatureFlags.Realtime) - const { chainId: appChainId } = useWeb3React() - +function usePendingTransactions(chainId?: ChainId) { const multichainTransactions = useMultichainTransactions() - const pendingTransactions = useMemo(() => { - // We can skip polling when the app's current chain is supported by the subscription service. - if (realtimeEnabled && appChainId && SUBSCRIPTION_CHAINIDS.includes(appChainId)) { - return [] - } - - return multichainTransactions.flatMap( - ([tx, chainId]) => (chainId === appChainId && isPendingTx(tx) ? tx : []) // Only use pending transactions on the current chain. - ) - }, [appChainId, multichainTransactions, realtimeEnabled]) + return useMemo(() => { + if (!chainId) return [] + return multichainTransactions.flatMap(([tx, txChainId]) => { + if (isPendingTx(tx) && txChainId === chainId) { + return tx + } else { + return [] + } + }) + }, [chainId, multichainTransactions]) +} +export function usePollPendingTransactions(onActivityUpdate: OnActivityUpdate) { + const realtimeEnabled = useFeatureFlag(FeatureFlags.Realtime) const { account, chainId, provider } = useWeb3React() + + const pendingTransactions = usePendingTransactions( + // We can skip polling when the app's current chain is supported by the subscription service. + realtimeEnabled && chainId && SUBSCRIPTION_CHAINIDS.includes(chainId) ? undefined : chainId + ) const hasPending = pendingTransactions.length > 0 - const lastBlockNumber = useBlockNumber() const blockTimestamp = useCurrentBlockTimestamp(hasPending ? undefined : NEVER_RELOAD) + const lastBlockNumber = useBlockNumber() const fastForwardBlockNumber = useFastForwardBlockNumber() const removeTransaction = useTransactionRemover() const dispatch = useAppDispatch() @@ -127,7 +118,7 @@ export function usePollPendingTransactions(onReceiveUpdate: ActivityUpdaterFn) { promise .then((receipt) => { fastForwardBlockNumber(receipt.blockNumber) - onReceiveUpdate({ + onActivityUpdate({ type: 'transaction', originalTransaction: tx, receipt, @@ -152,7 +143,7 @@ export function usePollPendingTransactions(onReceiveUpdate: ActivityUpdaterFn) { pendingTransactions, fastForwardBlockNumber, hasPending, - onReceiveUpdate, dispatch, + onActivityUpdate, ]) } diff --git a/apps/web/src/state/activity/subscription.ts b/apps/web/src/state/activity/subscription.ts new file mode 100644 index 00000000000..73afd239195 --- /dev/null +++ b/apps/web/src/state/activity/subscription.ts @@ -0,0 +1,43 @@ +import { RPC_PROVIDERS } from 'constants/providers' +import { useAssetActivitySubscription } from 'graphql/data/apollo/AssetActivityProvider' +import { useCallback, useEffect, useRef } from 'react' +import { toSerializableReceipt } from 'state/activity/utils' +import { usePendingOrders } from 'state/signatures/hooks' +import { OrderActivity, parseRemote as parseRemoteSignature } from 'state/signatures/parseRemote' +import { UniswapXOrderDetails } from 'state/signatures/types' +import { OnActivityUpdate, OrderUpdate } from './types' + +export function useOnAssetActivity(onActivityUpdate: OnActivityUpdate) { + const result = useAssetActivitySubscription() + const activity = result.data?.onAssetActivity + + // Updates should only trigger from the AssetActivity subscription, so the pendingOrders are behind a ref. + const pendingOrders = useRef([]) + pendingOrders.current = usePendingOrders() + + const updateOrder = useCallback( + async (activity: OrderActivity) => { + const signature = parseRemoteSignature(activity as OrderActivity) + const originalSignature = pendingOrders.current.find((order) => order.id === signature.id) ?? signature + const receipt = signature.txHash + ? toSerializableReceipt(await RPC_PROVIDERS[signature.chainId].getTransactionReceipt(signature.txHash)) + : undefined + const update: OrderUpdate = { + type: 'signature', + updatedStatus: signature.status, + originalSignature, + chainId: signature.chainId, + updatedSwapInfo: signature.swapInfo, + receipt, + } + onActivityUpdate(update) + }, + [onActivityUpdate] + ) + + useEffect(() => { + if (activity?.details.__typename === 'SwapOrderDetails') { + updateOrder(activity as OrderActivity) + } + }, [activity, updateOrder]) +} diff --git a/apps/web/src/state/activity/types.ts b/apps/web/src/state/activity/types.ts new file mode 100644 index 00000000000..41ac5858474 --- /dev/null +++ b/apps/web/src/state/activity/types.ts @@ -0,0 +1,26 @@ +import { UniswapXOrderStatus } from 'types/uniswapx' +import { TransactionInfo } from '../transactions/types' + +import { SupportedInterfaceChain } from 'constants/chains' +import { SignatureDetails } from 'state/signatures/types' +import { SerializableTransactionReceipt, TransactionDetails } from '../transactions/types' + +type TransactionUpdate = { + type: 'transaction' + originalTransaction: TransactionDetails & { info: T } + receipt: SerializableTransactionReceipt + chainId: SupportedInterfaceChain + updatedTransactionInfo?: T +} + +export type OrderUpdate = { + type: 'signature' + updatedStatus: UniswapXOrderStatus + originalSignature: SignatureDetails + receipt?: SerializableTransactionReceipt + chainId: SupportedInterfaceChain + updatedSwapInfo?: SignatureDetails['swapInfo'] +} + +export type ActivityUpdate = TransactionUpdate | OrderUpdate +export type OnActivityUpdate = (update: ActivityUpdate) => void diff --git a/apps/web/src/state/activity/updater.tsx b/apps/web/src/state/activity/updater.tsx index d115491bb08..1903c9196b1 100644 --- a/apps/web/src/state/activity/updater.tsx +++ b/apps/web/src/state/activity/updater.tsx @@ -5,41 +5,40 @@ import { useAddPopup } from 'state/application/hooks' import { PopupType } from 'state/application/reducer' import { useAppDispatch } from 'state/hooks' import { updateSignature } from 'state/signatures/reducer' -import { SignatureDetails, SignatureType } from 'state/signatures/types' +import { SignatureType } from 'state/signatures/types' import { logSwapSuccess } from 'tracing/swapFlowLoggers' import { UniswapXOrderStatus } from 'types/uniswapx' import { isL2ChainId } from 'utils/chains' import { addTransaction, finalizeTransaction } from '../transactions/reducer' -import { - SerializableTransactionReceipt, - TransactionDetails, - TransactionInfo, - TransactionType, -} from '../transactions/types' -import { usePollPendingOrders } from './orders' -import { usePollPendingTransactions } from './transactions' +import { TransactionInfo, TransactionType } from '../transactions/types' +import { usePollPendingOrders } from './polling/orders' +import { usePollPendingTransactions } from './polling/transactions' +import { useOnAssetActivity } from './subscription' +import { ActivityUpdate, OnActivityUpdate } from './types' -type TransactionUpdate = { - type: 'transaction' - originalTransaction: TransactionDetails & { info: T } - receipt: SerializableTransactionReceipt - chainId: number - updatedTransactionInfo?: T +export function ActivityStateUpdater() { + const onActivityUpdate = useOnActivityUpdate() + return ( + <> + + {/* The polling updater is present to update activity states for chains that are not supported by the subscriptions service. */} + + + ) } -type OrderUpdate = { - type: 'signature' - updatedStatus: UniswapXOrderStatus - originalSignature: SignatureDetails - receipt?: SerializableTransactionReceipt - chainId: number - updatedSwapInfo?: SignatureDetails['swapInfo'] +function SubscriptionActivityStateUpdater({ onActivityUpdate }: { onActivityUpdate: OnActivityUpdate }) { + useOnAssetActivity(onActivityUpdate) + return null } -type ActivityUpdate = TransactionUpdate | OrderUpdate -export type ActivityUpdaterFn = (update: ActivityUpdate) => void +function PollingActivityStateUpdater({ onActivityUpdate }: { onActivityUpdate: OnActivityUpdate }) { + usePollPendingTransactions(onActivityUpdate) + usePollPendingOrders(onActivityUpdate) + return null +} -function useUpdateActivityState() { +function useOnActivityUpdate() { const dispatch = useAppDispatch() const addPopup = useAddPopup() const analyticsContext = useTrace() @@ -62,7 +61,12 @@ function useUpdateActivityState() { const { originalSignature, updatedStatus, receipt, chainId, updatedSwapInfo } = update const info = updatedSwapInfo ?? originalSignature.swapInfo - const updatedOrder = { ...originalSignature, status: updatedStatus, swapInfo: info } + const updatedOrder = { + ...originalSignature, + status: updatedStatus, + swapInfo: info, + txHash: receipt?.transactionHash, + } dispatch(updateSignature(updatedOrder)) if (receipt && updatedStatus === UniswapXOrderStatus.FILLED) { @@ -85,29 +89,3 @@ function useUpdateActivityState() { [addPopup, analyticsContext, dispatch] ) } - -export function ActivityStateUpdater() { - return ( - <> - - {/* The polling updater is present to update activity states for chains that are not supported by the subscriptions service. */} - - - ) -} - -function SubscriptionBasedUpdater() { - // TODO(subs): Implement subscription-based activity state updates - // const updateActivityState = useUpdateActivityState() - // useOnReceiveActivityFromSubscription(updateActivityState) - - return null -} - -function PollingBasedUpdater() { - const updateActivityState = useUpdateActivityState() - usePollPendingTransactions(updateActivityState) - usePollPendingOrders(updateActivityState) - - return null -} diff --git a/apps/web/src/state/activity/utils.ts b/apps/web/src/state/activity/utils.ts new file mode 100644 index 00000000000..e6a6f754a2b --- /dev/null +++ b/apps/web/src/state/activity/utils.ts @@ -0,0 +1,15 @@ +import { TransactionReceipt } from '@ethersproject/abstract-provider' +import { SerializableTransactionReceipt } from 'state/transactions/types' + +export function toSerializableReceipt(receipt: TransactionReceipt): SerializableTransactionReceipt { + return { + blockHash: receipt.blockHash, + blockNumber: receipt.blockNumber, + contractAddress: receipt.contractAddress, + from: receipt.from, + status: receipt.status, + to: receipt.to, + transactionHash: receipt.transactionHash, + transactionIndex: receipt.transactionIndex, + } +} diff --git a/apps/web/src/state/limit/hooks.ts b/apps/web/src/state/limit/hooks.ts index f3f507f8760..b223c048f44 100644 --- a/apps/web/src/state/limit/hooks.ts +++ b/apps/web/src/state/limit/hooks.ts @@ -12,8 +12,8 @@ import { LimitOrderTrade, RouterPreference, SubmittableTrade, SwapFeeInfo, WrapI import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { getUSDCostPerGas, isClassicTrade } from 'state/routing/utils' import { useSwapAndLimitContext } from 'state/swap/hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { expiryToDeadlineSeconds } from './expiryToDeadlineSeconds' export type LimitInfo = { diff --git a/apps/web/src/state/lists/reducer.test.ts b/apps/web/src/state/lists/reducer.test.ts index 5cbad8447e0..50c57589cfb 100644 --- a/apps/web/src/state/lists/reducer.test.ts +++ b/apps/web/src/state/lists/reducer.test.ts @@ -1,4 +1,3 @@ -import tokenSafetyLookup from 'constants/tokenSafetyLookup' import { createStore, Store } from 'redux' import { updateVersion } from 'state/global/actions' @@ -81,15 +80,10 @@ describe('list reducer', () => { }) describe('fulfilled', () => { - beforeEach(() => { - jest.spyOn(tokenSafetyLookup, 'update').mockReturnValue(undefined) - }) - it('saves the list', () => { store.dispatch( fetchTokenList.fulfilled({ tokenList: STUB_TOKEN_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalled() expect(store.getState()).toEqual({ byUrl: { 'fake-url': { @@ -107,11 +101,9 @@ describe('list reducer', () => { store.dispatch( fetchTokenList.fulfilled({ tokenList: STUB_TOKEN_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalled() store.dispatch( fetchTokenList.fulfilled({ tokenList: STUB_TOKEN_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalledTimes(1) // should not be called again expect(store.getState()).toEqual({ byUrl: { 'fake-url': { @@ -129,11 +121,9 @@ describe('list reducer', () => { store.dispatch( fetchTokenList.fulfilled({ tokenList: STUB_TOKEN_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalled() store.dispatch( fetchTokenList.fulfilled({ tokenList: PATCHED_STUB_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalledTimes(1) // should not be called again expect(store.getState()).toEqual({ byUrl: { 'fake-url': { @@ -150,11 +140,9 @@ describe('list reducer', () => { store.dispatch( fetchTokenList.fulfilled({ tokenList: STUB_TOKEN_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalled() store.dispatch( fetchTokenList.fulfilled({ tokenList: MINOR_UPDATED_STUB_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalledTimes(1) // should not be called again expect(store.getState()).toEqual({ byUrl: { 'fake-url': { @@ -171,11 +159,9 @@ describe('list reducer', () => { store.dispatch( fetchTokenList.fulfilled({ tokenList: STUB_TOKEN_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalled() store.dispatch( fetchTokenList.fulfilled({ tokenList: MAJOR_UPDATED_STUB_LIST, requestId: 'request-id', url: 'fake-url' }) ) - expect(tokenSafetyLookup.update).toHaveBeenCalledTimes(1) // should not be called again expect(store.getState()).toEqual({ byUrl: { 'fake-url': { diff --git a/apps/web/src/state/lists/reducer.ts b/apps/web/src/state/lists/reducer.ts index 3057df419c7..8bfe2415ac0 100644 --- a/apps/web/src/state/lists/reducer.ts +++ b/apps/web/src/state/lists/reducer.ts @@ -1,6 +1,5 @@ import { createReducer } from '@reduxjs/toolkit' import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists' -import tokenSafetyLookup from 'constants/tokenSafetyLookup' import { ListsState } from 'state/lists/types' import { DEFAULT_LIST_OF_LISTS } from '../../constants/lists' @@ -65,7 +64,6 @@ export default createReducer(initialState, (builder) => loadingRequestId: null, error: null, } - tokenSafetyLookup.update(state) } }) .addCase(fetchTokenList.rejected, (state, { payload: { url, requestId, errorMessage } }) => { diff --git a/apps/web/src/state/lists/updater.ts b/apps/web/src/state/lists/updater.ts index fc18347fb3a..ba92bd1d252 100644 --- a/apps/web/src/state/lists/updater.ts +++ b/apps/web/src/state/lists/updater.ts @@ -1,18 +1,18 @@ import { getVersionUpgrade, VersionUpgrade } from '@uniswap/token-lists' import { useWeb3React } from '@web3-react/core' import { DEFAULT_LIST_OF_LISTS, UNSUPPORTED_LIST_URLS } from 'constants/lists' -import TokenSafetyLookupTable from 'constants/tokenSafetyLookup' import { useStateRehydrated } from 'hooks/useStateRehydrated' import useInterval from 'lib/hooks/useInterval' import ms from 'ms' import { useCallback, useEffect } from 'react' -import { useAppDispatch, useAppSelector } from 'state/hooks' +import { useAppDispatch } from 'state/hooks' import { useAllLists } from 'state/lists/hooks' import { useFetchListCallback } from '../../hooks/useFetchListCallback' import useIsWindowVisible from '../../hooks/useIsWindowVisible' import { acceptListUpdate } from './actions' +// TODO(WEB-3839): delete this when lists are removed from redux export default function Updater(): null { const { provider } = useWeb3React() const dispatch = useAppDispatch() @@ -20,13 +20,8 @@ export default function Updater(): null { // get all loaded lists, and the active urls const lists = useAllLists() - const listsState = useAppSelector((state) => state.lists) const rehydrated = useStateRehydrated() - useEffect(() => { - if (rehydrated) TokenSafetyLookupTable.update(listsState) - }, [listsState, rehydrated]) - const fetchList = useFetchListCallback() const fetchAllListsCallback = useCallback(() => { if (!isWindowVisible) return diff --git a/apps/web/src/state/routing/usePreviewTrade.ts b/apps/web/src/state/routing/usePreviewTrade.ts index f07bce2d6dc..4c0b80c23db 100644 --- a/apps/web/src/state/routing/usePreviewTrade.ts +++ b/apps/web/src/state/routing/usePreviewTrade.ts @@ -3,8 +3,8 @@ import { ChainId, Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/ import { ZERO_PERCENT } from 'constants/misc' import useIsWindowVisible from 'hooks/useIsWindowVisible' import { useMemo } from 'react' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { useGetQuickRouteQuery, useGetQuickRouteQueryState } from './quickRouteSlice' import { GetQuickQuoteArgs, PreviewTrade, QuoteState, TradeState } from './types' import { currencyAddressForSwapQuote } from './utils' diff --git a/apps/web/src/state/signatures/__snapshots__/parseRemote.test.ts.snap b/apps/web/src/state/signatures/__snapshots__/parseRemote.test.ts.snap new file mode 100644 index 00000000000..e98f8102aec --- /dev/null +++ b/apps/web/src/state/signatures/__snapshots__/parseRemote.test.ts.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`parseRemote should parse expired UniswapX order 1`] = ` +Object { + "addedTime": 10000, + "chainId": 1, + "encodedOrder": undefined, + "expiry": undefined, + "id": "someId", + "offerer": "someOfferer", + "orderHash": "someHash", + "status": "expired", + "swapInfo": Object { + "expectedOutputCurrencyAmountRaw": "200000000000000000000", + "inputCurrencyAmountRaw": "100000000000000000000", + "inputCurrencyId": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "isUniswapXOrder": true, + "minimumOutputCurrencyAmountRaw": "200000000000000000000", + "outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "settledOutputCurrencyAmountRaw": undefined, + "tradeType": 0, + "type": 1, + }, + "txHash": undefined, + "type": undefined, +} +`; + +exports[`parseRemote should parse filledUniswapX order 1`] = ` +Object { + "addedTime": 10000, + "chainId": 1, + "encodedOrder": undefined, + "expiry": undefined, + "id": "someId", + "offerer": "someOfferer", + "orderHash": "someHash", + "status": "filled", + "swapInfo": Object { + "expectedOutputCurrencyAmountRaw": "200000000000000000000", + "inputCurrencyAmountRaw": "100000000000000000000", + "inputCurrencyId": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "isUniswapXOrder": true, + "minimumOutputCurrencyAmountRaw": "200000000000000000000", + "outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "settledOutputCurrencyAmountRaw": "200000000000000000000", + "tradeType": 0, + "type": 1, + }, + "txHash": "someHash", + "type": undefined, +} +`; + +exports[`parseRemote should parse open UniswapX order 1`] = ` +Object { + "addedTime": 10000, + "chainId": 1, + "encodedOrder": undefined, + "expiry": undefined, + "id": "someId", + "offerer": "someOfferer", + "orderHash": "someHash", + "status": "open", + "swapInfo": Object { + "expectedOutputCurrencyAmountRaw": "200000000000000000000", + "inputCurrencyAmountRaw": "100000000000000000000", + "inputCurrencyId": "0x6B175474E89094C44Da98b954EedeAC495271d0F", + "isUniswapXOrder": true, + "minimumOutputCurrencyAmountRaw": "200000000000000000000", + "outputCurrencyId": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "settledOutputCurrencyAmountRaw": undefined, + "tradeType": 0, + "type": 1, + }, + "txHash": undefined, + "type": undefined, +} +`; diff --git a/apps/web/src/state/signatures/fixtures.ts b/apps/web/src/state/signatures/fixtures.ts new file mode 100644 index 00000000000..523ccbad997 --- /dev/null +++ b/apps/web/src/state/signatures/fixtures.ts @@ -0,0 +1,109 @@ +import { WETH9 } from '@uniswap/sdk-core' +import { DAI } from 'constants/tokens' +import { + AssetActivityPartsFragment, + Chain, + SwapOrderStatus, + TokenStandard, +} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' + +const MockOrderTimestamp = 10000 + +const mockAssetActivityPartsFragment = { + __typename: 'AssetActivity', + id: 'activityId', + timestamp: MockOrderTimestamp, + chain: Chain.Ethereum, + details: { + __typename: 'SwapOrderDetails', + id: 'detailsId', + offerer: 'offererId', + hash: 'someHash', + inputTokenQuantity: '100', + outputTokenQuantity: '200', + orderStatus: SwapOrderStatus.Open, + inputToken: { + __typename: 'Token', + id: 'tokenId', + chain: Chain.Ethereum, + standard: TokenStandard.Erc20, + }, + outputToken: { + __typename: 'Token', + id: 'tokenId', + chain: Chain.Ethereum, + standard: TokenStandard.Erc20, + }, + }, +} + +const mockSwapOrderDetailsPartsFragment = { + __typename: 'SwapOrderDetails', + id: 'someId', + offerer: 'someOfferer', + hash: 'someHash', + inputTokenQuantity: '100', + outputTokenQuantity: '200', + orderStatus: SwapOrderStatus.Open, + inputToken: { + __typename: 'Token', + id: DAI.address, + name: 'DAI', + symbol: DAI.symbol, + address: DAI.address, + decimals: 18, + chain: Chain.Ethereum, + standard: TokenStandard.Erc20, + project: { + __typename: 'TokenProject', + id: 'projectId', + isSpam: false, + logo: { + __typename: 'Image', + id: 'imageId', + url: 'someUrl', + }, + }, + }, + outputToken: { + __typename: 'Token', + id: WETH9[1].address, + name: 'Wrapped Ether', + symbol: 'WETH', + address: WETH9[1].address, + decimals: 18, + chain: Chain.Ethereum, + standard: TokenStandard.Erc20, + project: { + __typename: 'TokenProject', + id: 'projectId', + isSpam: false, + logo: { + __typename: 'Image', + id: 'imageId', + url: 'someUrl', + }, + }, + }, +} + +export const MockOpenUniswapXOrder = { + ...mockAssetActivityPartsFragment, + details: mockSwapOrderDetailsPartsFragment, +} as AssetActivityPartsFragment + +export const MockExpiredUniswapXOrder = { + ...mockAssetActivityPartsFragment, + details: { + ...mockSwapOrderDetailsPartsFragment, + orderStatus: SwapOrderStatus.Expired, + }, +} as AssetActivityPartsFragment + +export const MockFilledUniswapXOrder = { + ...mockAssetActivityPartsFragment, + details: { + ...mockSwapOrderDetailsPartsFragment, + orderStatus: SwapOrderStatus.Filled, + }, +} as AssetActivityPartsFragment diff --git a/apps/web/src/state/signatures/hooks.ts b/apps/web/src/state/signatures/hooks.ts index 626736644cb..511d6adb337 100644 --- a/apps/web/src/state/signatures/hooks.ts +++ b/apps/web/src/state/signatures/hooks.ts @@ -1,5 +1,5 @@ -import { ChainId } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' +import { SupportedInterfaceChain } from 'constants/chains' import { useCallback, useMemo } from 'react' import { useDispatch } from 'react-redux' import { useAppSelector } from 'state/hooks' @@ -44,7 +44,7 @@ export function useAddOrder() { ( offerer: string, orderHash: string, - chainId: ChainId, + chainId: SupportedInterfaceChain, expiry: number, swapInfo: UniswapXOrderDetails['swapInfo'], encodedOrder: string, diff --git a/apps/web/src/state/signatures/parseRemote.test.ts b/apps/web/src/state/signatures/parseRemote.test.ts new file mode 100644 index 00000000000..46f3709c233 --- /dev/null +++ b/apps/web/src/state/signatures/parseRemote.test.ts @@ -0,0 +1,17 @@ +import { MockExpiredUniswapXOrder, MockFilledUniswapXOrder, MockOpenUniswapXOrder } from './fixtures' +import { OrderActivity, parseRemote } from './parseRemote' + +describe('parseRemote', () => { + it('should parse open UniswapX order', () => { + const result = parseRemote(MockOpenUniswapXOrder as OrderActivity) + expect(result).toMatchSnapshot() + }) + it('should parse expired UniswapX order', () => { + const result = parseRemote(MockExpiredUniswapXOrder as OrderActivity) + expect(result).toMatchSnapshot() + }) + it('should parse filledUniswapX order', () => { + const result = parseRemote(MockFilledUniswapXOrder as OrderActivity) + expect(result).toMatchSnapshot() + }) +}) diff --git a/apps/web/src/state/signatures/parseRemote.ts b/apps/web/src/state/signatures/parseRemote.ts new file mode 100644 index 00000000000..ac65922defc --- /dev/null +++ b/apps/web/src/state/signatures/parseRemote.ts @@ -0,0 +1,86 @@ +import { TradeType } from '@uniswap/sdk-core' +import { asSupportedChain } from 'constants/chains' +import { parseUnits } from 'ethers/lib/utils' +import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from 'graphql/data/util' +import store from 'state' +import { TransactionType as LocalTransactionType } from 'state/transactions/types' +import { UniswapXOrderStatus } from 'types/uniswapx' +import { + AssetActivityPartsFragment, + SwapOrderDetailsPartsFragment, + SwapOrderStatus, + SwapOrderType, +} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { currencyId } from 'utils/currencyId' +import { addSignature } from './reducer' +import { SignatureDetails, SignatureType, UniswapXOrderDetails } from './types' + +export type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment } + +const SIGNATURE_TYPE_MAP: { [key in SwapOrderType]: SignatureType } = { + [SwapOrderType.Limit]: SignatureType.SIGN_LIMIT, + [SwapOrderType.Dutch]: SignatureType.SIGN_UNISWAPX_ORDER, + [SwapOrderType.DutchV2]: SignatureType.SIGN_UNISWAPX_V2_ORDER, +} + +const ORDER_STATUS_MAP: { [key in SwapOrderStatus]: UniswapXOrderStatus } = { + [SwapOrderStatus.Open]: UniswapXOrderStatus.OPEN, + [SwapOrderStatus.Expired]: UniswapXOrderStatus.EXPIRED, + [SwapOrderStatus.Error]: UniswapXOrderStatus.ERROR, + [SwapOrderStatus.InsufficientFunds]: UniswapXOrderStatus.INSUFFICIENT_FUNDS, + [SwapOrderStatus.Filled]: UniswapXOrderStatus.FILLED, + [SwapOrderStatus.Cancelled]: UniswapXOrderStatus.CANCELLED, +} + +export function parseRemote({ chain, details, timestamp }: OrderActivity): SignatureDetails { + const supportedChain = asSupportedChain(supportedChainIdFromGQLChain(chain)) + if (!supportedChain) { + const error = new Error('Invalid activity from unsupported chain received from GQL') + logSentryErrorForUnsupportedChain({ extras: { details }, errorMessage: error.message }) + throw error + } + + const status = ORDER_STATUS_MAP[details.orderStatus] + const isFilled = status == UniswapXOrderStatus.FILLED + + const inputTokenQuantity = parseUnits(details.inputTokenQuantity, details.inputToken.decimals).toString() + const outputTokenQuantity = parseUnits(details.outputTokenQuantity, details.outputToken.decimals).toString() + + if (inputTokenQuantity === '0' || outputTokenQuantity === '0') { + // TODO(WEB-3765): This is a temporary mitigation for a bug where the backend sends "0.000000" for small amounts. + throw new Error('Invalid activity received from GQL') + } + + const signature: UniswapXOrderDetails = { + id: details.id, + type: SIGNATURE_TYPE_MAP[details.swapOrderType], + offerer: details.offerer, + chainId: supportedChain, + orderHash: details.hash, + expiry: details.expiry, + encodedOrder: details.encodedOrder, + status, + addedTime: timestamp, + // only if completed + txHash: isFilled ? details.hash : undefined, + + swapInfo: { + isUniswapXOrder: true, + type: LocalTransactionType.SWAP, + // This doesn't affect the display, but we don't know this value from the remote activity. + tradeType: TradeType.EXACT_INPUT, + inputCurrencyId: currencyId(gqlToCurrency(details.inputToken)), + outputCurrencyId: currencyId(gqlToCurrency(details.outputToken)), + inputCurrencyAmountRaw: inputTokenQuantity, + expectedOutputCurrencyAmountRaw: outputTokenQuantity, + minimumOutputCurrencyAmountRaw: outputTokenQuantity, + settledOutputCurrencyAmountRaw: isFilled ? outputTokenQuantity : undefined, + }, + } + + if (status === UniswapXOrderStatus.OPEN) { + store.dispatch(addSignature(signature)) + } + + return signature +} diff --git a/apps/web/src/state/signatures/types.ts b/apps/web/src/state/signatures/types.ts index 39790b57dc1..65a90b8095e 100644 --- a/apps/web/src/state/signatures/types.ts +++ b/apps/web/src/state/signatures/types.ts @@ -1,3 +1,4 @@ +import { SupportedInterfaceChain } from 'constants/chains' import { UniswapXOrderStatus } from 'types/uniswapx' import { ExactInputSwapTransactionInfo, ExactOutputSwapTransactionInfo } from '../transactions/types' @@ -11,7 +12,7 @@ interface BaseSignatureFields { type?: SignatureType id: string addedTime: number - chainId: number + chainId: SupportedInterfaceChain expiry?: number offerer: string } diff --git a/apps/web/src/state/swap/hooks.tsx b/apps/web/src/state/swap/hooks.tsx index 0b7793fba18..fadc0269fa5 100644 --- a/apps/web/src/state/swap/hooks.tsx +++ b/apps/web/src/state/swap/hooks.tsx @@ -46,8 +46,9 @@ export function useSwapActionHandlers(): { (field: Field, currency: Currency) => { const [currentCurrencyKey, otherCurrencyKey]: (keyof CurrencyState)[] = field === Field.INPUT ? ['inputCurrency', 'outputCurrency'] : ['outputCurrency', 'inputCurrency'] + const otherCurrency = currencyState[otherCurrencyKey] // the case where we have to swap the order - if (currency === currencyState[otherCurrencyKey]) { + if (otherCurrency && currency.equals(otherCurrency)) { setCurrencyState({ [currentCurrencyKey]: currency, [otherCurrencyKey]: currencyState[currentCurrencyKey], diff --git a/apps/web/src/test-utils/constants.ts b/apps/web/src/test-utils/constants.ts index 2e141797e2f..7019998daa4 100644 --- a/apps/web/src/test-utils/constants.ts +++ b/apps/web/src/test-utils/constants.ts @@ -1,18 +1,41 @@ -import { ChainId, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core' +import { ChainId, CurrencyAmount, Percent, Token, TradeType, WETH9 } from '@uniswap/sdk-core' // This is a test file, so the import of smart-order-router is allowed. // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { V3Route } from '@uniswap/smart-order-router' import { FeeAmount, Pool } from '@uniswap/v3-sdk' -import { DAI, USDC_MAINNET, nativeOnChain } from 'constants/tokens' +import { DAI, DAI_ARBITRUM_ONE, USDC_ARBITRUM, USDC_MAINNET, USDT, WBTC, nativeOnChain } from 'constants/tokens' import { BigNumber } from 'ethers/lib/ethers' import JSBI from 'jsbi' import { expiryToDeadlineSeconds } from 'state/limit/expiryToDeadlineSeconds' import { Expiry } from 'state/limit/types' import { ClassicTrade, DutchOrderTrade, LimitOrderTrade, PreviewTrade, QuoteMethod } from 'state/routing/types' +import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' export const TEST_TOKEN_1 = new Token(1, '0x0000000000000000000000000000000000000001', 18, 'ABC', 'Abc') +export const TEST_TOKEN_1_INFO: CurrencyInfo = { + currency: TEST_TOKEN_1, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png', + currencyId: 'ABC', + safetyLevel: SafetyLevel.Verified, +} export const TEST_TOKEN_2 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 'DEF', 'Def') +export const TEST_TOKEN_2_INFO: CurrencyInfo = { + currency: TEST_TOKEN_2, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png', + currencyId: 'DEF', + safetyLevel: SafetyLevel.Verified, +} export const TEST_TOKEN_3 = new Token(1, '0x0000000000000000000000000000000000000003', 18, 'GHI', 'Ghi') +export const TEST_TOKEN_3_INFO: CurrencyInfo = { + currency: TEST_TOKEN_3, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000003/logo.png', + currencyId: 'GHI', + safetyLevel: SafetyLevel.Verified, +} export const ETH_MAINNET = nativeOnChain(ChainId.MAINNET) export const TEST_POOL_12 = new Pool( @@ -202,3 +225,66 @@ export const LIMIT_ORDER_TRADE = new LimitOrderTrade({ swapper: '0xSwapperAddress', deadlineBufferSecs: expiryToDeadlineSeconds(Expiry.Week), }) + +export const NATIVE_INFO: CurrencyInfo = { + currency: ETH_MAINNET, + logoUrl: 'ethereum-logo.png', + currencyId: 'ETH', + safetyLevel: SafetyLevel.Verified, +} + +export const WETH_INFO: CurrencyInfo = { + currency: WETH9[ChainId.MAINNET], + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + currencyId: WETH9[ChainId.MAINNET].address, + safetyLevel: SafetyLevel.Verified, +} + +export const DAI_INFO: CurrencyInfo = { + currency: DAI, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png', + currencyId: DAI.address, + safetyLevel: SafetyLevel.Verified, +} + +export const USDC_INFO: CurrencyInfo = { + currency: USDC_MAINNET, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png', + currencyId: USDC_MAINNET.address, + safetyLevel: SafetyLevel.Verified, +} + +export const USDT_INFO: CurrencyInfo = { + currency: USDT, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png', + currencyId: USDT.address, + safetyLevel: SafetyLevel.Verified, +} + +export const WBTC_INFO: CurrencyInfo = { + currency: WBTC, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png', + currencyId: WBTC.address, + safetyLevel: SafetyLevel.Verified, +} + +export const DAI_ARBITRUM_INFO: CurrencyInfo = { + currency: DAI_ARBITRUM_ONE, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1/logo.png', + currencyId: DAI_ARBITRUM_ONE.address, + safetyLevel: SafetyLevel.Verified, +} + +export const USDC_ARBITRUM_INFO: CurrencyInfo = { + currency: USDC_ARBITRUM, + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xaf88d065e77c8cC2239327C5EDb3A432268e5831/logo.png', + currencyId: USDC_ARBITRUM.address, + safetyLevel: SafetyLevel.Verified, +} diff --git a/apps/web/src/test-utils/pools/fixtures.ts b/apps/web/src/test-utils/pools/fixtures.ts index 7d8624433ad..8df073a5975 100644 --- a/apps/web/src/test-utils/pools/fixtures.ts +++ b/apps/web/src/test-utils/pools/fixtures.ts @@ -21,6 +21,14 @@ export const validBEPoolToken0 = { address: validPoolToken0.id, chain: 'ETHEREUM', decimals: 6, + project: { + id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + tokens: [], + logo: { + id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + url: 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png', + }, + }, } as BEToken export const validUSDCCurrency = { @@ -51,6 +59,14 @@ export const validBEPoolToken1 = { address: validPoolToken1.id, chain: 'ETHEREUM', decimals: 18, + project: { + id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + tokens: [], + logo: { + id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + url: 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png', + }, + }, } as BEToken export const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c' diff --git a/apps/web/src/test-utils/tokens/mocks.ts b/apps/web/src/test-utils/tokens/mocks.ts new file mode 100644 index 00000000000..c947f1808be --- /dev/null +++ b/apps/web/src/test-utils/tokens/mocks.ts @@ -0,0 +1,60 @@ +// Import this file (test-utils/tokens/mock) at the top of a test file to use +// these predefined token lookup mocks. + +jest.mock('hooks/Tokens') + +import { ChainId, Currency, WETH9 } from '@uniswap/sdk-core' +import { COMMON_BASES } from 'constants/routing' +import { DAI, DAI_ARBITRUM_ONE, USDC_ARBITRUM, USDC_MAINNET, USDT, WBTC } from 'constants/tokens' +import { useCurrency, useCurrencyInfo } from 'hooks/Tokens' +import { + DAI_ARBITRUM_INFO, + DAI_INFO, + NATIVE_INFO, + TEST_TOKEN_1, + TEST_TOKEN_1_INFO, + TEST_TOKEN_2, + TEST_TOKEN_2_INFO, + TEST_TOKEN_3, + TEST_TOKEN_3_INFO, + USDC_ARBITRUM_INFO, + USDC_INFO, + USDT_INFO, + WBTC_INFO, + WETH_INFO, +} from 'test-utils/constants' +import { mocked } from 'test-utils/mocked' +import { isSameAddress } from 'utilities/src/addresses' + +beforeEach(() => { + // Global mocks for token lookups. To override in a test, use `mocked(useDefaultActiveTokens).mockImplementation(...)`. + mocked(useCurrency).mockImplementation((address?: string, chainId?: ChainId) => { + if (address?.toLowerCase() === DAI.address.toLowerCase()) { + return DAI + } + if (address?.toLowerCase() === WETH9[ChainId.MAINNET].address.toLowerCase()) { + return WETH9[ChainId.MAINNET] + } + if (address?.toLowerCase() === USDC_MAINNET.address.toLowerCase()) { + return USDC_MAINNET + } + return COMMON_BASES[chainId ?? ChainId.MAINNET]?.find((base) => + base.isNative ? base.symbol === 'ETH' : base.address === address + ) + }) + mocked(useCurrencyInfo).mockImplementation((currency?: Currency | string) => { + if (typeof currency !== 'string' && currency?.isNative) return NATIVE_INFO + const address = typeof currency === 'string' ? currency : currency?.address + if (isSameAddress(address, DAI.address)) return DAI_INFO + if (isSameAddress(address, USDC_MAINNET.address)) return USDC_INFO + if (isSameAddress(address, WETH9[ChainId.MAINNET].address)) return WETH_INFO + if (isSameAddress(address, USDT.address)) return USDT_INFO + if (isSameAddress(address, WBTC.address)) return WBTC_INFO + if (isSameAddress(address, DAI_ARBITRUM_ONE.address)) return DAI_ARBITRUM_INFO + if (isSameAddress(address, USDC_ARBITRUM.address)) return USDC_ARBITRUM_INFO + if (isSameAddress(address, TEST_TOKEN_1.address)) return TEST_TOKEN_1_INFO + if (isSameAddress(address, TEST_TOKEN_2.address)) return TEST_TOKEN_2_INFO + if (isSameAddress(address, TEST_TOKEN_3.address)) return TEST_TOKEN_3_INFO + return undefined + }) +}) diff --git a/apps/web/src/theme/components/text.tsx b/apps/web/src/theme/components/text.tsx index f25523a4600..c3d010d0673 100644 --- a/apps/web/src/theme/components/text.tsx +++ b/apps/web/src/theme/components/text.tsx @@ -133,7 +133,4 @@ export const ThemedText = { DeprecatedItalic(props: TextProps) { return }, - DeprecatedError({ error, ...props }: { error: boolean } & TextProps) { - return - }, } diff --git a/apps/web/src/tracing/index.ts b/apps/web/src/tracing/index.ts index c288f33df88..67416056782 100644 --- a/apps/web/src/tracing/index.ts +++ b/apps/web/src/tracing/index.ts @@ -22,7 +22,6 @@ const SENTRY_USER_ID_KEY = 'sentry-user-id' // Actual KEYs are set by proxy servers. const AMPLITUDE_DUMMY_KEY = '00000000000000000000000000000000' -export const STATSIG_DUMMY_KEY = 'client-0000000000000000000000000000000000000000000' Sentry.init({ dsn: process.env.REACT_APP_SENTRY_DSN, diff --git a/apps/web/src/utils/formatNumbers.test.ts b/apps/web/src/utils/formatNumbers.test.ts index 64f71224386..873875efa09 100644 --- a/apps/web/src/utils/formatNumbers.test.ts +++ b/apps/web/src/utils/formatNumbers.test.ts @@ -7,8 +7,8 @@ import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency' import { useActiveLocale } from 'hooks/useActiveLocale' import { mocked } from 'test-utils/mocked' import { Currency } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { NumberType, useFormatter } from './formatNumbers' jest.mock('hooks/useActiveLocale') diff --git a/apps/web/src/utils/formatNumbers.ts b/apps/web/src/utils/formatNumbers.ts index cd0f35d66bf..72c7b3dc6f4 100644 --- a/apps/web/src/utils/formatNumbers.ts +++ b/apps/web/src/utils/formatNumbers.ts @@ -13,8 +13,8 @@ import usePrevious from 'hooks/usePrevious' import { useCallback, useMemo } from 'react' import { Bound } from 'state/mint/v3/actions' import { Currency as GqlCurrency } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' type Nullish = T | null | undefined type NumberFormatOptions = Intl.NumberFormatOptions diff --git a/apps/web/src/utils/getInitialLogoURL.ts b/apps/web/src/utils/getInitialLogoURL.ts new file mode 100644 index 00000000000..0a9e13df197 --- /dev/null +++ b/apps/web/src/utils/getInitialLogoURL.ts @@ -0,0 +1,26 @@ +import { isCelo, nativeOnChain } from 'constants/tokens' +import { chainIdToNetworkName, getNativeLogoURI } from 'lib/hooks/useCurrencyLogoURIs' +import { isAddress } from 'utilities/src/addresses' +import celoLogo from '../assets/svg/celo_logo.svg' + +export function getInitialLogoUrl( + address?: string | null, + chainId?: number | null, + isNative?: boolean, + backupImg?: string | null +) { + if (chainId && isNative) return getNativeLogoURI(chainId) + + const networkName = chainId ? chainIdToNetworkName(chainId) : 'ethereum' + const checksummedAddress = isAddress(address) + + if (chainId && isCelo(chainId) && address === nativeOnChain(chainId).wrapped.address) { + return celoLogo + } + + if (checksummedAddress) { + return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${checksummedAddress}/logo.png` + } else { + return backupImg ?? undefined + } +} diff --git a/config/jest-presets/jest/jest-preset.js b/config/jest-presets/jest/jest-preset.js index eb679726720..93f190f6631 100644 --- a/config/jest-presets/jest/jest-preset.js +++ b/config/jest-presets/jest/jest-preset.js @@ -8,6 +8,7 @@ module.exports = { testEnvironment: 'jsdom', transform: { '\\.svg$': 'jest-transformer-svg', + '^.+\\.jsx?$': 'babel-jest', }, // coverageDirectory: '/coverage', // coverageReporters: ['json','lcov','html'], diff --git a/config/jest-presets/jest/setup.js b/config/jest-presets/jest/setup.js index 1b721b5a48c..2af854c1b97 100644 --- a/config/jest-presets/jest/setup.js +++ b/config/jest-presets/jest/setup.js @@ -115,8 +115,8 @@ const NetInfoStateType = { jest.mock('@react-native-community/netinfo', () => ({ ...mockRNCNetInfo, NetInfoStateType })) -jest.mock('statsig-react-native', () => { - const real = jest.requireActual('statsig-react-native') +jest.mock('uniswap/src/features/statsig/sdk/statsig', () => { + const real = jest.requireActual('uniswap/src/features/statsig/sdk/statsig') const StatsigMock = { ...real, useGate: () => { diff --git a/config/jest-presets/package.json b/config/jest-presets/package.json index 276ab67ae39..828c9caa290 100644 --- a/config/jest-presets/package.json +++ b/config/jest-presets/package.json @@ -6,6 +6,7 @@ }, "devDependencies": { "@testing-library/jest-native": "5.4.2", + "babel-jest": "29.6.1", "jest-svg-transformer": "1.0.0", "jest-transform-stub": "2.0.0", "mem-storage-area": "1.0.3" diff --git a/package.json b/package.json index c5ec5beff02..c1e8232abf6 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "local:check": "./scripts/local-version-check.sh", "postinstall": "husky install && yarn g:prepare", "stretch": "yarn workspace @uniswap/stretch", - "ui:build:icons": "yarn workspace ui build:icons", + "ui": "yarn workspace ui", "upgrade:tamagui": "yarn up '*tamagui*' '@tamagui/*'", "upgrade:tamagui:canary": "yarn up '*tamagui*'@canary '@tamagui/*'@canary", "wallet": "yarn workspace wallet", diff --git a/packages/ui/README.md b/packages/ui/README.md index e360546a491..9bb9eabc685 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -24,9 +24,14 @@ The `Button` element is used consistently to ensure action buttons maintain simi #### Icons and Logos -Icons and Logos are made available off a single import as `Icons.*` and `Logos.*`. These files are generated from placing the file in `packages/ui/src/assets/icons` or `packages/ui/src/assets/logos/svg` and running the generate command: +Icons and Logos are made available off a single import as `Icons.*` and `Logos.*`. These files are generated from placing the file in `packages/ui/src/assets/icons` or `packages/ui/src/assets/logos/svg` and running the generate command(s): -`yarn ui:build:icons` +```bash +# Generate all icons +yarn ui build:icons +# Generate any icons that do not yet have an existing TS file +yarn ui build:icons:missing +``` When adding an SVG, please ensure you replace color references as needed with `currentColor` to ensure the asset respects the color property when used. diff --git a/packages/ui/package.json b/packages/ui/package.json index 16119d82db2..43a2755338f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -48,6 +48,7 @@ "scripts": { "build": "tamagui-build --ignore-base-url", "build:icons": "node -r esbuild-register ./src/scripts/componentize-icons.ts", + "build:icons:missing": "yarn build:icons --skip-existing", "check:deps:usage": "depcheck", "lint": "eslint src --max-warnings=0", "lint:fix": "eslint src --fix", diff --git a/packages/ui/src/assets/icons/receive-alt.svg b/packages/ui/src/assets/icons/receive-alt.svg new file mode 100644 index 00000000000..ae6f50de024 --- /dev/null +++ b/packages/ui/src/assets/icons/receive-alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/components/button/Button.tsx b/packages/ui/src/components/button/Button.tsx index 2961d7b1e36..c86a251b029 100644 --- a/packages/ui/src/components/button/Button.tsx +++ b/packages/ui/src/components/button/Button.tsx @@ -124,7 +124,7 @@ const CustomButtonText = styled(Text, { size: { micro: { fontSize: '$micro', - fontWeight: '$micro', + fontWeight: '$medium', lineHeight: '$micro', }, medium: { @@ -134,12 +134,12 @@ const CustomButtonText = styled(Text, { }, small: { fontSize: '$small', - fontWeight: '$small', + fontWeight: '$medium', lineHeight: '$small', }, large: { fontSize: '$large', - fontWeight: '$large', + fontWeight: '$medium', lineHeight: '$large', }, }, diff --git a/packages/ui/src/components/icons/ReceiveAlt.tsx b/packages/ui/src/components/icons/ReceiveAlt.tsx new file mode 100644 index 00000000000..12f415599f7 --- /dev/null +++ b/packages/ui/src/components/icons/ReceiveAlt.tsx @@ -0,0 +1,19 @@ +import { Path, Svg } from 'react-native-svg' + +// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths +import { createIcon } from '../factories/createIcon' + +export const [ReceiveAlt, AnimatedReceiveAlt] = createIcon({ + name: 'ReceiveAlt', + getIcon: (props) => ( + + + + ), + defaultFill: '#7D7D7D', +}) diff --git a/packages/ui/src/components/icons/index.ts b/packages/ui/src/components/icons/index.ts index e7436d4c242..00e6c6185a3 100644 --- a/packages/ui/src/components/icons/index.ts +++ b/packages/ui/src/components/icons/index.ts @@ -134,6 +134,7 @@ export * from './QrCode' export * from './QuestionInCircle' export * from './Quotes' export * from './Receive' +export * from './ReceiveAlt' export * from './ReceiveArrow' export * from './ReceiveDots' export * from './RightArrow' diff --git a/packages/ui/src/components/text/Text.tsx b/packages/ui/src/components/text/Text.tsx index ff468008e41..036525f2ca8 100644 --- a/packages/ui/src/components/text/Text.tsx +++ b/packages/ui/src/components/text/Text.tsx @@ -1,13 +1,11 @@ import { PropsWithChildren } from 'react' -import { useWindowDimensions } from 'react-native' import { GetProps, styled, Text as TamaguiText } from 'tamagui' import { withAnimated } from 'ui/src/components/factories/animated' import { Flex } from 'ui/src/components/layout' import { HiddenFromScreenReaders } from 'ui/src/components/text/HiddenFromScreenReaders' import { Skeleton } from 'ui/src/loading/Skeleton' import { fonts } from 'ui/src/theme/fonts' - -export const DEFAULT_FONT_SCALE = 1 +import { useEnableFontScaling } from './useEnableFontScaling' export const TextFrame = styled(TamaguiText, { fontFamily: '$body', @@ -180,8 +178,7 @@ export const Text = ({ loadingPlaceholderText = '000.00', ...rest }: TextProps): JSX.Element => { - const { fontScale } = useWindowDimensions() - const enableFontScaling = allowFontScaling ?? fontScale > DEFAULT_FONT_SCALE + const enableFontScaling = useEnableFontScaling(allowFontScaling) if (loading) { return ( diff --git a/packages/ui/src/components/text/useEnableFontScaling.native.tsx b/packages/ui/src/components/text/useEnableFontScaling.native.tsx new file mode 100644 index 00000000000..a04f30fed12 --- /dev/null +++ b/packages/ui/src/components/text/useEnableFontScaling.native.tsx @@ -0,0 +1,8 @@ +import { useWindowDimensions } from 'react-native' + +const DEFAULT_FONT_SCALE = 1 + +export const useEnableFontScaling = (allowFontScaling?: boolean): boolean => { + const { fontScale } = useWindowDimensions() + return allowFontScaling ?? fontScale > DEFAULT_FONT_SCALE +} diff --git a/packages/ui/src/components/text/useEnableFontScaling.tsx b/packages/ui/src/components/text/useEnableFontScaling.tsx new file mode 100644 index 00000000000..1dcfdade5d1 --- /dev/null +++ b/packages/ui/src/components/text/useEnableFontScaling.tsx @@ -0,0 +1,8 @@ +/** + * Web doesn't support font scaling so disabling here avoiding using an expensive hook + */ + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const useEnableFontScaling = (allowFontScaling?: boolean): boolean => { + return false +} diff --git a/packages/ui/src/scripts/componentize-icons.ts b/packages/ui/src/scripts/componentize-icons.ts index ff4f8520e03..b240bda5d4d 100644 --- a/packages/ui/src/scripts/componentize-icons.ts +++ b/packages/ui/src/scripts/componentize-icons.ts @@ -4,23 +4,28 @@ import camelcase from 'camelcase' import { load } from 'cheerio' import { ESLint } from 'eslint' -import { ensureDir, readdir, readFile, writeFile } from 'fs-extra' +import { ensureDirSync, existsSync, readFileSync, readdirSync, writeFileSync } from 'fs-extra' import path, { join } from 'path' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import uppercamelcase from 'uppercamelcase' -interface SVGDirectorySourceAndOutput { +// Types + +interface DirectoryPair { input: string output: string } +// Main Loop + async function run(): Promise { + const skipExisting = process.argv.length > 2 && process.argv[2] === '--skip-existing' const srcDir = join(__dirname, '..') const assetsDir = join(srcDir, 'assets') - const svgDirPairs: SVGDirectorySourceAndOutput[] = [ + const svgDirPairs: DirectoryPair[] = [ { input: join(assetsDir, 'icons'), output: join(srcDir, 'components', 'icons'), @@ -32,192 +37,221 @@ async function run(): Promise { ] for (const dirPair of svgDirPairs) { - await generateSVGComponents(dirPair) + await createSVGComponents(dirPair, skipExisting) } } -async function generateSVGComponents(directoryPair: SVGDirectorySourceAndOutput): Promise { +// Logic Functions + +async function createSVGComponents(dirs: DirectoryPair, skipExisting: boolean): Promise { + // Ensure output directory exists + ensureDirSync(dirs.output) + let indexFile = `` + const fileNames = readdirSync(dirs.input).filter((name) => name.endsWith('.svg')) - await ensureDir(directoryPair.output) + for (const fileName of fileNames) { + const className = generateClassName(fileName) + const inputPath = join(dirs.input, fileName) + const outputPath = path.join(dirs.output, `${className}.tsx`) - const fileNames = (await readdir(directoryPair.input)).filter((name) => name.endsWith('.svg')) + // Add to index file even if it exists + indexFile += `\nexport * from './${className}'` - for (const svgFileName of fileNames) { - try { - const iconPath = join(directoryPair.input, svgFileName) - const svg = await readFile(iconPath, 'utf-8') - const id = path.basename(svgFileName, '.svg') - const $ = load(svg, { - xmlMode: true, - }) - const cname = uppercamelcase(id) - const fileName = `${cname}.tsx` - const outPath = path.join(directoryPair.output, fileName) - - // Because CSS does not exist on Native platforms - // We need to duplicate the styles applied to the - // SVG to its children - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const svgAttribs = $('svg')[0]!.attribs - delete svgAttribs.xmlns - const attribsOfInterest: Record = {} - - Object.keys(svgAttribs).forEach((key) => { - if ( - ![ - 'height', - 'width', - 'viewBox', - 'fill', - 'stroke-width', - 'stroke-linecap', - 'stroke-linejoin', - ].includes(key) - ) { - attribsOfInterest[key] = svgAttribs[key] - } - }) + if (skipExisting && existsSync(outputPath)) { + continue + } + + // Generate and write file + const svg = readFileSync(inputPath, 'utf-8') + const element = await generateSVGComponent(svg, fileName) + if (element) { + console.log(`🦄 ${fileName}`) + writeFileSync(outputPath, element, 'utf-8') + } + } + + // Format and write index file + console.log('Writing index file...') + const formattedIndex = await eslintFormat(indexFile, 'index.ts') + writeFileSync(join(dirs.output, 'index.ts'), formattedIndex, 'utf-8') +} - $('*').each((_, el: any) => { - Object.keys(el.attribs).forEach((x) => { - if (x.includes('-')) { - $(el).attr(camelcase(x), el.attribs[x]).removeAttr(x) - } - if (x === 'stroke') { - $(el).attr(x, 'currentColor') - } - }) - - // For every element that is NOT svg ... - if (el.name !== 'svg') { - Object.keys(attribsOfInterest).forEach((key) => { - $(el).attr(camelcase(key), attribsOfInterest[key]) - }) - } - - if (el.name === 'svg') { - $(el).attr('otherProps', '...') - } +async function generateSVGComponent(svg: string, fileName: string): Promise { + try { + const element = generateSVGComponentString(svg, fileName) + return await eslintFormat(element, fileName) + } catch (err) { + console.log(`Error converting icon: ${fileName}: ${(err as any).message}`) + } +} + +// Core SVG File Generation + +function generateSVGComponentString(svg: string, fileName: string): string { + const $ = load(svg, { + xmlMode: true, + }) + + const className = generateClassName(fileName) + + // Because CSS does not exist on Native platforms + // We need to duplicate the styles applied to the + // SVG to its children + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const svgAttribs = $('svg')[0]!.attribs + delete svgAttribs.xmlns + const attribsOfInterest: Record = {} + + Object.keys(svgAttribs).forEach((key) => { + if ( + ![ + 'height', + 'width', + 'viewBox', + 'fill', + 'stroke-width', + 'stroke-linecap', + 'stroke-linejoin', + ].includes(key) + ) { + attribsOfInterest[key] = svgAttribs[key] + } + }) + + $('*').each((_, el: any) => { + Object.keys(el.attribs).forEach((x) => { + if (x.includes('-')) { + $(el).attr(camelcase(x), el.attribs[x]).removeAttr(x) + } + if (x === 'stroke') { + $(el).attr(x, 'currentColor') + } + }) + + // For every element that is NOT svg ... + if (el.name !== 'svg') { + Object.keys(attribsOfInterest).forEach((key) => { + $(el).attr(camelcase(key), attribsOfInterest[key]) }) + } + + if (el.name === 'svg') { + $(el).attr('otherProps', '...') + } + }) - const parsedSvgToReact = $('svg') - .toString() - .replace(/ class=\"[^\"]+\"/g, '') - .replace(/ version=\"[^\"]+\"/g, '') - .replace(/width="[0-9]+"/, '') - .replace(/height="[0-9]+"/, '') - .replace(' ( - ${parsedSvgToReact.replace('otherProps="..."', '{...props}')} - ), - ${defaultFill ? `defaultFill: '${defaultFill}'` : ''} +export const [${className}, Animated${className}] = createIcon({ +name: '${className}', +getIcon: (props) => ( + ${parsedSvgToReact.replace('otherProps="..."', '{...props}')} +), +${defaultFill ? `defaultFill: '${defaultFill}'` : ''} }) ` + .replace(/fill="(#[a-z0-9]+)"/gi, `fill={"currentColor" ?? '$1'}`) + .replaceAll(`xmlns:xlink="http://www.w3.org/1999/xlink"`, '') + .replaceAll(`xlink:href`, 'xlinkHref') +} - // if no width/height/color, add them - - element = element.replace(/fill="(#[a-z0-9]+)"/gi, `fill={"currentColor" ?? '$1'}`) - element = element.replaceAll(`xmlns:xlink="http://www.w3.org/1999/xlink"`, '') - element = element.replaceAll(`xlink:href`, 'xlinkHref') - - const formatted = await eslintFormat(element) - - if (formatted) { - element = formatted - } else { - console.warn(`not linted ${svgFileName}`) - } - - await writeFile(outPath, element, 'utf-8') - - indexFile += `\nexport * from './${fileName.replace('.tsx', '')}'` - - console.log(`🦄 ${svgFileName}`) - } catch (err) { - console.log(`Error converting icon: ${svgFileName}: ${(err as any).message}`) - } - } +// Helpers - const formattedIndex = await eslintFormat(indexFile) - await writeFile(join(directoryPair.output, 'index.ts'), formattedIndex, 'utf-8') +function generateClassName(fileName: string): string { + return uppercamelcase(path.basename(fileName, '.svg')) as string } +// Linting + const eslint = new ESLint({ fix: true }) -async function eslintFormat(inSource: string): Promise { - const out = await eslint.lintText(inSource, { +async function eslintFormat(source: string, name: string): Promise { + const out = await eslint.lintText(source, { // eslint wants a file to use for determining format and it actually has to exist 🙄 filePath: './src/scripts/componentize-icons-eslint-dummy-file.tsx', }) - return out?.[0]?.output + + const lintedFile = out?.[0]?.output + + if (lintedFile) { + return lintedFile + } else { + console.warn(`not linted ${name}`) + return source + } } +// This must be at the end to run all code + run().catch(() => undefined) diff --git a/packages/ui/src/theme/fonts.ts b/packages/ui/src/theme/fonts.ts index 1cef942cc4a..cf1a91ab7c1 100644 --- a/packages/ui/src/theme/fonts.ts +++ b/packages/ui/src/theme/fonts.ts @@ -157,10 +157,9 @@ export const headingFont = createFont({ large: fonts.heading1.fontSize, }, weight: { - small: '500', + book: '400', medium: '500', - true: '500', - large: '500', + true: fonts.heading1.fontWeight, }, lineHeight: { small: fonts.heading3.lineHeight, @@ -179,10 +178,9 @@ export const subHeadingFont = createFont({ true: fonts.subheading1.fontSize, }, weight: { - small: '500', + book: '400', medium: '500', - large: '500', - true: '500', + true: fonts.subheading1.fontWeight, }, lineHeight: { small: fonts.subheading2.lineHeight, @@ -205,11 +203,9 @@ export const bodyFont = createFont({ true: fonts.body2.fontSize, }, weight: { - micro: fonts.body3.fontWeight, - small: fonts.body2.fontWeight, - medium: fonts.body2.fontWeight, - large: fonts.body1.fontWeight, - true: fonts.body2.fontWeight, + book: '400', + medium: '500', + true: fonts.body1.fontWeight, }, lineHeight: { micro: fonts.body3.lineHeight, @@ -230,11 +226,9 @@ export const buttonFont = createFont({ true: fonts.buttonLabel2.fontSize, }, weight: { - micro: fonts.buttonLabel4.fontWeight, - small: fonts.buttonLabel3.fontWeight, - medium: fonts.buttonLabel2.fontWeight, - large: fonts.buttonLabel1.fontWeight, - true: fonts.buttonLabel2.fontWeight, + book: '400', + medium: '500', + true: fonts.buttonLabel1.fontWeight, }, lineHeight: { micro: fonts.buttonLabel4.lineHeight, diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql index 99eab0cd9cc..7cdf026155b 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql @@ -439,6 +439,7 @@ query TransactionHistoryUpdater($addresses: [String!]!) { query Token($chain: Chain!, $address: String) { token(chain: $chain, address: $address) { + id symbol decimals chain @@ -743,17 +744,23 @@ query TopTokens( pageSize: $pageSize orderBy: $orderBy ) { + id address chain - decimals symbol + name + decimals standard project { id name - isSpam - logoUrl + logo { + id + url + } safetyLevel + logoUrl + isSpam } } } diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql index 19837f49c1c..7cedf5bfe1b 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql @@ -2,14 +2,15 @@ directive @defer on FIELD """ -Tells the service this field/object has access authorized by an API key. -""" -directive @aws_api_key on OBJECT | FIELD_DEFINITION - -""" -Tells the service this field/object has access authorized by sigv4 signing. +Tells the service which subscriptions will be published to when this mutation is +called. This directive is deprecated use @aws_susbscribe directive instead. """ -directive @aws_iam on OBJECT | FIELD_DEFINITION +directive @aws_publish( + """ + List of subscriptions which will be published to when this mutation is called. + """ + subscriptions: [String] +) on FIELD_DEFINITION """ Tells the service this field/object has access authorized by a Cognito User Pools token. @@ -20,39 +21,38 @@ directive @aws_cognito_user_pools( ) on OBJECT | FIELD_DEFINITION """ -Tells the service which subscriptions will be published to when this mutation is -called. This directive is deprecated use @aws_susbscribe directive instead. +Tells the service this field/object has access authorized by a Lambda Authorizer. """ -directive @aws_publish( +directive @aws_lambda on OBJECT | FIELD_DEFINITION + +"""Tells the service which mutation triggers this subscription.""" +directive @aws_subscribe( """ - List of subscriptions which will be published to when this mutation is called. + List of mutations which will trigger this subscription when they are called. """ - subscriptions: [String] + mutations: [String] +) on FIELD_DEFINITION + +"""Directs the schema to enforce authorization on a field""" +directive @aws_auth( + """List of cognito user pool groups which have access on this field""" + cognito_groups: [String] ) on FIELD_DEFINITION """ -Tells the service this field/object has access authorized by a Lambda Authorizer. +Tells the service this field/object has access authorized by an API key. """ -directive @aws_lambda on OBJECT | FIELD_DEFINITION +directive @aws_api_key on OBJECT | FIELD_DEFINITION """ Tells the service this field/object has access authorized by an OIDC token. """ directive @aws_oidc on OBJECT | FIELD_DEFINITION -"""Directs the schema to enforce authorization on a field""" -directive @aws_auth( - """List of cognito user pool groups which have access on this field""" - cognito_groups: [String] -) on FIELD_DEFINITION - -"""Tells the service which mutation triggers this subscription.""" -directive @aws_subscribe( - """ - List of mutations which will trigger this subscription when they are called. - """ - mutations: [String] -) on FIELD_DEFINITION +""" +Tells the service this field/object has access authorized by sigv4 signing. +""" +directive @aws_iam on OBJECT | FIELD_DEFINITION """ Types, unions, and inputs (alphabetized): diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/SimpleToken.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/SimpleToken.graphql index 6dba48ebeab..7d641922978 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/SimpleToken.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/SimpleToken.graphql @@ -1,20 +1,26 @@ -query SimpleToken($chain: Chain!, $address: String = null) { - token(chain: $chain, address: $address) { +fragment SimpleTokenDetails on Token { + id + address + chain + symbol + name + decimals + standard + project { + id + name + logo { id - address - chain - symbol - name - decimals - standard - project { - id - logo { - id - url - } - safetyLevel - isSpam - } + url } + safetyLevel + logoUrl + isSpam + } +} + +query SimpleToken($chain: Chain!, $address: String = null) { + token(chain: $chain, address: $address) { + ...SimpleTokenDetails } +} diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql index f1778967c66..36a76f98454 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql @@ -31,19 +31,22 @@ fragment NFTTransferParts on NftTransfer { fragment TokenAssetParts on Token { id - name - symbol address - decimals chain + symbol + name + decimals standard project { id - isSpam + name logo { id url } + safetyLevel + logoUrl + isSpam } } @@ -177,4 +180,4 @@ subscription OnAssetActivity($subscriptionId: ID!, $account: String!) { onAssetActivity(subscriptionId: $subscriptionId, addresses: [$account]) { ...AssetActivityParts } -} \ No newline at end of file +} diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/pool.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/pool.graphql index a71aa170141..0748cc6ca7a 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/pool.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/pool.graphql @@ -1,19 +1,3 @@ -fragment PoolToken on Token { - id - address - name - symbol - chain - decimals - project { - id - logo { - id - url - } - } -} - query V3Pool($chain: Chain!, $address: String!) { v3Pool(chain: $chain, address: $address) { id @@ -21,25 +5,25 @@ query V3Pool($chain: Chain!, $address: String!) { address feeTier token0 { - ...PoolToken + ...SimpleTokenDetails project { - id - markets(currencies: [USD]) { - id - price { id - value + markets(currencies: [USD]) { + id + price { + id + value + } + } + logo { + id + url + } } } - logo { - id - url - } - } - } token0Supply token1 { - ...PoolToken + ...SimpleTokenDetails project { id markets(currencies: [USD]) { @@ -119,7 +103,7 @@ query V2Pair($address: String!) { protocolVersion address token0 { - ...PoolToken + ...SimpleTokenDetails project { id markets(currencies: [USD]) { @@ -137,7 +121,7 @@ query V2Pair($address: String!) { } token0Supply token1 { - ...PoolToken + ...SimpleTokenDetails project { id markets(currencies: [USD]) { diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/portfolios.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/portfolios.graphql index 2ba0e9cc8b6..b6263ffc8d5 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/portfolios.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/portfolios.graphql @@ -7,13 +7,25 @@ fragment PortfolioTokenBalanceParts on TokenBalance { value } token { + ...SimpleTokenDetails id - chain address - name + chain symbol - standard + name decimals + standard + project { + id + name + logo { + id + url + } + safetyLevel + logoUrl + isSpam + } } tokenProjectMarket { id diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/search.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/search.graphql index 49bfea3bdb5..a39ef630db0 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/search.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/search.graphql @@ -1,5 +1,6 @@ query TrendingTokens($chain: Chain!) { topTokens(pageSize: 4, page: 1, chain: $chain, orderBy: VOLUME) { + ...SimpleTokenDetails id decimals name @@ -34,6 +35,7 @@ query TrendingTokens($chain: Chain!) { query SearchTokensWeb($searchQuery: String!, $chains: [Chain!]) { searchTokens(searchQuery: $searchQuery, chains: $chains) { + ...SimpleTokenDetails id decimals name @@ -69,12 +71,26 @@ query SearchTokensWeb($searchQuery: String!, $chains: [Chain!]) { query TopTokens100($duration: HistoryDuration!, $chain: Chain!) { topTokens(pageSize: 100, page: 1, chain: $chain, orderBy: VOLUME) { - id - name - chain - address - symbol - standard + ...SimpleTokenDetails + project { + id + name + logo { + id + url + } + safetyLevel + logoUrl + isSpam + markets(currencies: [USD]) { + id + fullyDilutedValuation { + id + value + currency + } + } + } market(currency: USD) { id totalValueLocked { @@ -126,9 +142,7 @@ query TopTokens100($duration: HistoryDuration!, $chain: Chain!) { # We separately query sparkline data so that the large download time does not block Token Explore rendering query TopTokensSparkline($duration: HistoryDuration!, $chain: Chain!) { topTokens(pageSize: 100, page: 1, chain: $chain, orderBy: VOLUME) { - id - address - chain + ...SimpleTokenDetails market(currency: USD) { id priceHistory(duration: $duration) { diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/topPools.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/topPools.graphql index f55645a369b..52178e474c6 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/topPools.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/topPools.graphql @@ -8,10 +8,10 @@ query TopV3Pools($chain: Chain!, $first: Int!, $cursor: Float, $tokenAddress: St } feeTier token0 { - ...PoolToken + ...SimpleTokenDetails } token1 { - ...PoolToken + ...SimpleTokenDetails } txCount volume24h: cumulativeVolume(duration: DAY) { @@ -32,10 +32,10 @@ query TopV2Pairs($first: Int!, $cursor: Float, $tokenAddress: String) { value } token0 { - ...PoolToken + ...SimpleTokenDetails } token1 { - ...PoolToken + ...SimpleTokenDetails } txCount volume24h: cumulativeVolume(duration: DAY) { diff --git a/packages/uniswap/src/features/experiments/constants.ts b/packages/uniswap/src/features/experiments/constants.ts deleted file mode 100644 index 99e6d2bbbf3..00000000000 --- a/packages/uniswap/src/features/experiments/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Experiment names - * These should match Experiment Name on Statsig - */ -export enum ExperimentsWallet { - OnboardingNewCreateImportFlow = 'onboarding-ab-1', -} - -/** - * Experiment parameter names - * - * These should match parameter names on Statsig within an experiment - */ -export enum ExperimentParamsWallet { - Enabled = 'enabled', -} - -// Add experiment values here as needed. -export const EXPERIMENT_VALUES_BY_EXPERIMENT: Record< - string, - Record> -> = {} - -// Dummy key since we use the reverse proxy will handle the real key -export const DUMMY_STATSIG_SDK_KEY = 'client-000000000000000000000000000000000000000000' diff --git a/packages/uniswap/src/features/experiments/hooks.ts b/packages/uniswap/src/features/experiments/hooks.ts deleted file mode 100644 index 192b98e35c6..00000000000 --- a/packages/uniswap/src/features/experiments/hooks.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { DynamicConfigs, getConfigName } from 'uniswap/src/features/experiments/configs' -import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' -import { - DynamicConfig, - useConfig, - useExperiment, - useExperimentWithExposureLoggingDisabled, - useGate, - useGateWithExposureLoggingDisabled, -} from 'uniswap/src/features/experiments/statsig/statsig' -import { ExperimentParamsWallet, ExperimentsWallet } from './constants' - -export function useFeatureFlag(flag: FeatureFlags): boolean { - const name = getFeatureFlagName(flag) - const { value } = useGate(name) - return value -} - -export function useFeatureFlagWithExposureLoggingDisabled(flag: FeatureFlags): boolean { - const name = getFeatureFlagName(flag) - const { value } = useGateWithExposureLoggingDisabled(name) - return value -} - -export function useExperimentEnabled(experimentName: ExperimentsWallet): boolean { - return useExperiment(experimentName).config.getValue(ExperimentParamsWallet.Enabled) as boolean -} - -export function useExperimentEnabledWithExposureLoggingDisabled( - experimentName: ExperimentsWallet -): boolean { - return useExperimentWithExposureLoggingDisabled(experimentName).config.getValue( - ExperimentParamsWallet.Enabled - ) as boolean -} - -export function useDynamicConfig(config: DynamicConfigs): DynamicConfig { - const name = getConfigName(config) - const { config: dynamicConfig } = useConfig(name) - return dynamicConfig -} diff --git a/packages/uniswap/src/features/experiments/statsig/statsig.native.ts b/packages/uniswap/src/features/experiments/statsig/statsig.native.ts deleted file mode 100644 index 4942f8cf865..00000000000 --- a/packages/uniswap/src/features/experiments/statsig/statsig.native.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { - DynamicConfig, - useConfig, - useExperiment, - useExperimentWithExposureLoggingDisabled, - useGate, - useGateWithExposureLoggingDisabled, -} from 'statsig-react-native' diff --git a/packages/uniswap/src/features/experiments/statsig/statsig.ts b/packages/uniswap/src/features/experiments/statsig/statsig.ts deleted file mode 100644 index f50aede642f..00000000000 --- a/packages/uniswap/src/features/experiments/statsig/statsig.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { - DynamicConfig, - useConfig, - useExperiment, - useExperimentWithExposureLoggingDisabled, - useGate, - useGateWithExposureLoggingDisabled, -} from 'statsig-react' diff --git a/packages/uniswap/src/features/experiments/configs.ts b/packages/uniswap/src/features/statsig/configs.ts similarity index 94% rename from packages/uniswap/src/features/experiments/configs.ts rename to packages/uniswap/src/features/statsig/configs.ts index edefe1bbee9..5b7cabc62e9 100644 --- a/packages/uniswap/src/features/experiments/configs.ts +++ b/packages/uniswap/src/features/statsig/configs.ts @@ -8,6 +8,7 @@ import { logger } from 'utilities/src/logger/logger' export enum DynamicConfigs { // Wallet MobileForceUpgrade, + Slippage, UwuLink, // Web @@ -21,6 +22,7 @@ export const WEB_CONFIG_NAMES = new Map([ export const WALLET_CONFIG_NAMES = new Map([ [DynamicConfigs.MobileForceUpgrade, 'force_upgrade'], [DynamicConfigs.UwuLink, 'uwulink_config'], + [DynamicConfigs.Slippage, 'slippage_configs'], ]) export function getConfigName(config: DynamicConfigs): string { diff --git a/packages/uniswap/src/features/statsig/constants.ts b/packages/uniswap/src/features/statsig/constants.ts new file mode 100644 index 00000000000..b51a0add08d --- /dev/null +++ b/packages/uniswap/src/features/statsig/constants.ts @@ -0,0 +1,2 @@ +// Dummy key since we use the reverse proxy will handle the real key +export const DUMMY_STATSIG_SDK_KEY = 'client-000000000000000000000000000000000000000000' diff --git a/packages/uniswap/src/features/statsig/experiments.ts b/packages/uniswap/src/features/statsig/experiments.ts new file mode 100644 index 00000000000..8a3681afa62 --- /dev/null +++ b/packages/uniswap/src/features/statsig/experiments.ts @@ -0,0 +1,46 @@ +import { isInterface } from 'uniswap/src/utils/platform' +import { logger } from 'utilities/src/logger/logger' + +export enum BooleanExperimentValues { + Enabled = 'enabled', + Disabled = 'disabled', +} + +export const DEFAULT_EXPERIMENT_ENABLED_VALUE = BooleanExperimentValues.Enabled + +// Experiment definition structure +export interface Experiment { + name: string + key: string + values: string[] +} + +/** + * Experiment parameter names + * + * These should match parameter names on Statsig within an experiment + */ +export enum Experiments {} + +export const WEB_EXPERIMENTS = new Map([]) + +export const WALLET_EXPERIMENTS = new Map([]) + +export function getExperimentDefinition(experiment: Experiments): Experiment { + const names = isInterface ? WEB_EXPERIMENTS : WALLET_EXPERIMENTS + const experimentDef = names.get(experiment) + if (!experimentDef) { + const err = new Error( + `Experiment ${Experiments[experiment]} does not have a mapping for this application` + ) + logger.error(err, { + tags: { + file: 'experiments.ts', + function: 'getExperiment', + }, + }) + throw err + } + + return experimentDef +} diff --git a/packages/uniswap/src/features/experiments/flags.ts b/packages/uniswap/src/features/statsig/flags.ts similarity index 100% rename from packages/uniswap/src/features/experiments/flags.ts rename to packages/uniswap/src/features/statsig/flags.ts diff --git a/packages/uniswap/src/features/statsig/hooks.ts b/packages/uniswap/src/features/statsig/hooks.ts new file mode 100644 index 00000000000..5f94d9c729d --- /dev/null +++ b/packages/uniswap/src/features/statsig/hooks.ts @@ -0,0 +1,76 @@ +import { DynamicConfigs, getConfigName } from 'uniswap/src/features/statsig/configs' +import { + DEFAULT_EXPERIMENT_ENABLED_VALUE, + Experiments, + getExperimentDefinition, +} from 'uniswap/src/features/statsig/experiments' +import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags' +import { + DynamicConfig, + useConfig, + useExperiment, + useExperimentWithExposureLoggingDisabled, + useGate, + useGateWithExposureLoggingDisabled, +} from 'uniswap/src/features/statsig/sdk/statsig' +import { logger } from 'utilities/src/logger/logger' + +export function useFeatureFlag(flag: FeatureFlags): boolean { + const name = getFeatureFlagName(flag) + const { value } = useGate(name) + return value +} + +export function useFeatureFlagWithExposureLoggingDisabled(flag: FeatureFlags): boolean { + const name = getFeatureFlagName(flag) + const { value } = useGateWithExposureLoggingDisabled(name) + return value +} + +export function useExperimentEnabled( + experiment: Experiments, + checkValue: string = DEFAULT_EXPERIMENT_ENABLED_VALUE +): boolean { + const experimentDef = getExperimentDefinition(experiment) + const value = useExperiment(experimentDef.name).config.getValue(experimentDef.key) + return checkValue === value +} + +export function useExperimentEnabledWithExposureLoggingDisabled( + experiment: Experiments, + checkValue: string = DEFAULT_EXPERIMENT_ENABLED_VALUE +): boolean { + const experimentDef = getExperimentDefinition(experiment) + const value = useExperimentWithExposureLoggingDisabled(experimentDef.name).config.getValue( + experimentDef.key + ) + return checkValue === value +} + +export function useExperimentValueWithExposureLoggingDisabled(experiment: Experiments): string { + const experimentDef = getExperimentDefinition(experiment) + const value = useExperimentWithExposureLoggingDisabled(experimentDef.name).config.getValue( + experimentDef.key + ) + + if (typeof value !== 'string') { + const err = new Error( + `Experiment ${Experiments[experiment]} does not have a properly mapped value` + ) + logger.error(err, { + tags: { + file: 'hooks.ts', + function: 'useExperimentValueWithExposureLoggingDisabled', + }, + }) + throw err + } + + return value +} + +export function useDynamicConfig(config: DynamicConfigs): DynamicConfig { + const name = getConfigName(config) + const { config: dynamicConfig } = useConfig(name) + return dynamicConfig +} diff --git a/packages/uniswap/src/features/statsig/sdk/statsig.native.ts b/packages/uniswap/src/features/statsig/sdk/statsig.native.ts new file mode 100644 index 00000000000..aa63790995f --- /dev/null +++ b/packages/uniswap/src/features/statsig/sdk/statsig.native.ts @@ -0,0 +1,13 @@ +import { Statsig, StatsigContext } from 'statsig-react-native' +const statsig = Statsig +const statsigContext = StatsigContext + +export { + DynamicConfig, + useConfig, + useExperiment, + useExperimentWithExposureLoggingDisabled, + useGate, + useGateWithExposureLoggingDisabled, +} from 'statsig-react-native' +export { statsig as Statsig, statsigContext as StatsigContext } diff --git a/packages/uniswap/src/features/statsig/sdk/statsig.ts b/packages/uniswap/src/features/statsig/sdk/statsig.ts new file mode 100644 index 00000000000..bcf2885328d --- /dev/null +++ b/packages/uniswap/src/features/statsig/sdk/statsig.ts @@ -0,0 +1,13 @@ +import { Statsig, StatsigContext } from 'statsig-react' +const statsig = Statsig +const statsigContext = StatsigContext + +export { + DynamicConfig, + useConfig, + useExperiment, + useExperimentWithExposureLoggingDisabled, + useGate, + useGateWithExposureLoggingDisabled, +} from 'statsig-react' +export { statsig as Statsig, statsigContext as StatsigContext } diff --git a/packages/uniswap/src/i18n/locales/source/en-US.json b/packages/uniswap/src/i18n/locales/source/en-US.json index 31622172fb9..622e514514e 100644 --- a/packages/uniswap/src/i18n/locales/source/en-US.json +++ b/packages/uniswap/src/i18n/locales/source/en-US.json @@ -209,6 +209,7 @@ "notNow": "Not now", "ok": "OK", "paste": "Paste", + "pay": "Pay", "receive": "Receive", "remove": "Remove", "restore": "Restore", @@ -266,6 +267,8 @@ "systemSettings": "Settings" }, "text": { + "connected": "Connected", + "disconnected": "Disconnected", "error": "Error", "loading": "Loading", "notAvailable": "N/A", @@ -1266,11 +1269,6 @@ "security": "Security", "support": "Support", "wallet": { - "action": { - "hide": "Hide wallets", - "showAll_one": "Show one wallet", - "showAll_other": "Show all {{count}} wallets" - }, "button": { "viewAll": "View all", "viewLess": "View less" @@ -2059,6 +2057,11 @@ } } }, + "uwulink": { + "error": { + "insufficientTokens": "Not enough {{tokenSymbol}} on {{chain}}" + } + }, "walletConnect": { "dapps": { "connection": "Connected to {{dappNameOrUrl}}", diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 5fb0fc5bfbb..9f3200f6493 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -67,7 +67,6 @@ "redux": "4.2.1", "redux-saga": "1.2.2", "redux-saga-test-plan": "4.0.4", - "statsig-react-native": "4.11.0", "typed-redux-saga": "1.5.0", "ui": "workspace:^", "uniswap": "workspace:^", diff --git a/packages/wallet/src/components/BaseCard/__snapshots__/BaseCard.test.tsx.snap b/packages/wallet/src/components/BaseCard/__snapshots__/BaseCard.test.tsx.snap index 24b095d873c..3a0ca6a3180 100644 --- a/packages/wallet/src/components/BaseCard/__snapshots__/BaseCard.test.tsx.snap +++ b/packages/wallet/src/components/BaseCard/__snapshots__/BaseCard.test.tsx.snap @@ -33,7 +33,7 @@ exports[`EmptyState renders without error 1`] = ` } > dappName: string } @@ -242,7 +242,7 @@ export function DappLogoWithWCBadge({ size, chainId, }: { - dappImageUrl: string | null + dappImageUrl: Maybe dappName: string size: number chainId: ChainId | null diff --git a/packages/wallet/src/components/CurrencyLogo/TokenLogo.tsx b/packages/wallet/src/components/CurrencyLogo/TokenLogo.tsx index 2019875e710..7212f57106b 100644 --- a/packages/wallet/src/components/CurrencyLogo/TokenLogo.tsx +++ b/packages/wallet/src/components/CurrencyLogo/TokenLogo.tsx @@ -1,7 +1,7 @@ -import { memo, useMemo } from 'react' +import { memo } from 'react' import { Image } from 'react-native' import { Flex, Text, useIsDarkMode, useSporeColors } from 'ui/src' -import { fonts, iconSizes, spacing } from 'ui/src/theme' +import { iconSizes, spacing, validColor } from 'ui/src/theme' import { useLogolessColorScheme } from 'ui/src/utils/colors' import { isSVGUri, uriToHttp } from 'utilities/src/format/urls' import { STATUS_RATIO } from 'wallet/src/components/CurrencyLogo/CurrencyLogo' @@ -75,11 +75,6 @@ export const TokenLogo = memo(function _TokenLogo({ ? logolessColorScheme.dark : logolessColorScheme.light - const textStyle = useMemo( - () => ({ color: foreground, fontFamily: fonts.buttonLabel3.family, fontWeight: '500' }), - [foreground] - ) - return ( {httpUri ? ( @@ -95,11 +90,14 @@ export const TokenLogo = memo(function _TokenLogo({ width={size}> + numberOfLines={1}> {symbol?.slice(0, 3)} diff --git a/packages/wallet/src/components/QRCodeScanner/QRCode.tsx b/packages/wallet/src/components/QRCodeScanner/QRCode.tsx index 08a357d162d..06738db466d 100644 --- a/packages/wallet/src/components/QRCodeScanner/QRCode.tsx +++ b/packages/wallet/src/components/QRCodeScanner/QRCode.tsx @@ -11,8 +11,8 @@ import { useUniconColors, } from 'ui/src' import { borderRadii } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { isAndroid } from 'uniswap/src/utils/platform' import QRCode from 'wallet/src/components/QRCodeScanner/custom-qr-code-generator' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' diff --git a/packages/wallet/src/components/WalletConnect/DappIconPlaceholder.tsx b/packages/wallet/src/components/WalletConnect/DappIconPlaceholder.tsx index 44f4cbf49b1..4c6c5ccad8f 100644 --- a/packages/wallet/src/components/WalletConnect/DappIconPlaceholder.tsx +++ b/packages/wallet/src/components/WalletConnect/DappIconPlaceholder.tsx @@ -5,7 +5,7 @@ export function DappIconPlaceholder({ name, iconSize, }: { - name: string + name?: string iconSize: number }): JSX.Element { return ( @@ -21,7 +21,7 @@ export function DappIconPlaceholder({ color="$neutral2" textAlign="center" variant={iconSize >= iconSizes.icon40 ? 'subheading1' : 'body2'}> - {name.length > 0 ? name.charAt(0) : ' '} + {name && name.length > 0 ? name.charAt(0) : ' '}
) diff --git a/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap b/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap index 437b9aab7fe..45084bb59b0 100644 --- a/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap +++ b/packages/wallet/src/components/WalletPreviewCard/__snapshots__/WalletPreviewCard.test.tsx.snap @@ -315,7 +315,7 @@ exports[`renders wallet preview card 1`] = ` > diff --git a/packages/wallet/src/components/network/__snapshots__/NetworkFee.test.tsx.snap b/packages/wallet/src/components/network/__snapshots__/NetworkFee.test.tsx.snap index cec192feb38..189876b54e5 100644 --- a/packages/wallet/src/components/network/__snapshots__/NetworkFee.test.tsx.snap +++ b/packages/wallet/src/components/network/__snapshots__/NetworkFee.test.tsx.snap @@ -48,7 +48,7 @@ exports[`NetworkFee renders a NetworkFee in a loading state 1`] = ` } > void }): JSX.Element { - const { menuActions, onContextMenuPress } = useNFTMenu({ + const { menuActions, onContextMenuPress } = useNFTContextMenu({ contractAddress: item.contractAddress, tokenId: item.tokenId, owner, diff --git a/packages/wallet/src/components/text/__snapshots__/LearnMoreLink.test.tsx.snap b/packages/wallet/src/components/text/__snapshots__/LearnMoreLink.test.tsx.snap index 1f146fb5985..7d8d766509c 100644 --- a/packages/wallet/src/components/text/__snapshots__/LearnMoreLink.test.tsx.snap +++ b/packages/wallet/src/components/text/__snapshots__/LearnMoreLink.test.tsx.snap @@ -27,7 +27,7 @@ exports[`LearnMoreLink renders without error 1`] = ` } > void navigateToTokenDetails: (currencyId: string) => void navigateToReceive: () => void - navigateToSend: (args: NavigateToSendArgs) => void + navigateToSend: (args: NavigateToSendFlowArgs) => void handleShareNft: (args: ShareNftArgs) => void handleShareToken: (args: ShareTokenArgs) => void } diff --git a/packages/wallet/src/features/auth/saga.ts b/packages/wallet/src/features/auth/saga.ts index ade4de087e5..e29dc59bb4f 100644 --- a/packages/wallet/src/features/auth/saga.ts +++ b/packages/wallet/src/features/auth/saga.ts @@ -1,7 +1,6 @@ -import { call, put } from 'typed-redux-saga' +import { call } from 'typed-redux-saga' import { logger } from 'utilities/src/logger/logger' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' -import { lockWallet, unlockWallet } from 'wallet/src/features/wallet/slice' import { createMonitoredSaga } from 'wallet/src/utils/saga' import { AuthActionType, AuthBaseParams, AuthSagaError, UnlockParams } from './types' @@ -18,9 +17,7 @@ function* auth(params: AuthBaseParams) { function* unlock({ password }: UnlockParams) { logger.debug('authSaga', 'unlock', `Unlocking wallet`) const success = yield* call(Keyring.unlock, password) - if (success) { - yield* put(unlockWallet()) - } else { + if (!success) { throw new Error(AuthSagaError.InvalidPassword) } } @@ -28,7 +25,6 @@ function* unlock({ password }: UnlockParams) { function* lock() { logger.debug('authSaga', 'lock', `Locking wallet`) yield* call(Keyring.lock) - yield* put(lockWallet()) } export const { diff --git a/packages/wallet/src/features/fiatCurrency/conversion.ts b/packages/wallet/src/features/fiatCurrency/conversion.ts index cd1c2714b1e..6e3109ce381 100644 --- a/packages/wallet/src/features/fiatCurrency/conversion.ts +++ b/packages/wallet/src/features/fiatCurrency/conversion.ts @@ -3,8 +3,8 @@ import { Currency, useConvertQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { FiatNumberType } from 'utilities/src/format/types' import { PollingInterval } from 'wallet/src/constants/misc' import { FiatCurrency } from 'wallet/src/features/fiatCurrency/constants' diff --git a/packages/wallet/src/features/fiatCurrency/hooks.ts b/packages/wallet/src/features/fiatCurrency/hooks.ts index eab6e7eebf6..2cfd6f35c3c 100644 --- a/packages/wallet/src/features/fiatCurrency/hooks.ts +++ b/packages/wallet/src/features/fiatCurrency/hooks.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' // eslint-disable-next-line no-restricted-imports import { FiatCurrencyComponents, getFiatCurrencyComponents } from 'utilities/src/format/localeBased' import { FiatCurrency } from 'wallet/src/features/fiatCurrency/constants' diff --git a/packages/wallet/src/features/fiatOnRamp/api.ts b/packages/wallet/src/features/fiatOnRamp/api.ts index 7bfe62dde20..e9fda49865e 100644 --- a/packages/wallet/src/features/fiatOnRamp/api.ts +++ b/packages/wallet/src/features/fiatOnRamp/api.ts @@ -7,6 +7,7 @@ import { ONE_MINUTE_MS } from 'utilities/src/time/time' import { createSignedRequestParams, objectToQueryString } from 'wallet/src/data/utils' import { walletContextValue } from 'wallet/src/features/wallet/context' +import { REQUEST_SOURCE, getVersionHeader } from 'uniswap/src/data/constants' import { FORGetCountryResponse, FORQuoteRequest, @@ -223,6 +224,13 @@ export const fiatOnRampAggregatorApi = createApi({ reducerPath: 'fiatOnRampAggregatorApi', baseQuery: fetchBaseQuery({ baseUrl: config.fiatOnRampApiUrl, + headers: { + 'Content-Type': 'application/json', + 'X-API-KEY': config.uniswapApiKey, + 'x-request-source': REQUEST_SOURCE, + 'x-app-version': getVersionHeader(), + Origin: config.uniswapAppUrl, + }, }), endpoints: (builder) => ({ fiatOnRampAggregatorCountryList: builder.query({ diff --git a/packages/wallet/src/features/gas/adjustGasFee.ts b/packages/wallet/src/features/gas/adjustGasFee.ts index 22073d0bedb..f14ffb715d6 100644 --- a/packages/wallet/src/features/gas/adjustGasFee.ts +++ b/packages/wallet/src/features/gas/adjustGasFee.ts @@ -1,4 +1,5 @@ import { BigNumber, BigNumberish, providers } from 'ethers' +import { logger } from 'utilities/src/logger/logger' import { FeeType, GasFeeResult } from 'wallet/src/features/gas/types' import { BigNumberMax } from 'wallet/src/utils/number' @@ -48,7 +49,77 @@ export function getAdjustedGasFeeDetails( } } - throw new Error('Transaction request has no gas values') + const error = determineError(request, currentGasFeeParams) + logger.error(error, { + tags: { file: 'adjustGasFee.ts', function: 'getAdjustedGasFeeDetails' }, + extra: { request, currentGasFeeParams }, + }) + throw error +} + +function determineError( + request: providers.TransactionRequest, + currentGasFeeParams: NonNullable +): Error { + const isEIP1559Transaction = + request.maxFeePerGas !== undefined && request.maxPriorityFeePerGas !== undefined + const isEIP1559Params = + 'maxFeePerGas' in currentGasFeeParams && 'maxPriorityFeePerGas' in currentGasFeeParams + const isLegacyTransaction = request.gasPrice !== undefined + const isLegacyParams = 'gasPrice' in currentGasFeeParams + + // General error if all detection flags are false + if (!isEIP1559Transaction && !isLegacyTransaction && !isEIP1559Params && !isLegacyParams) { + return new Error('Unable to determine gas fee structure. No gas values or parameters found.') + } + + // Handle missing gas values on transaction + const transactionMissingGasInfo = !isEIP1559Transaction && !isLegacyTransaction + if (transactionMissingGasInfo) { + if (isEIP1559Params) { + return new Error( + 'Transaction is missing gas values, but gasParams were provided for an EIP-1559 transaction.' + ) + } else { + return new Error( + 'Transaction is missing gas values, but currentGasFeeParams were provided for a legacy transaction.' + ) + } + } + + // Handling missing gas fee parameters + const missingGasParams = !isEIP1559Params && !isLegacyParams + if (missingGasParams) { + if (isEIP1559Transaction) { + return new Error( + 'currentGasFeeParams is missing gas fee parameters. Required: maxFeePerGas and maxPriorityFeePerGas for EIP-1559 transactions.' + ) + } else { + return new Error( + 'currentGasFeeParams is missing gas fee parameters. Required: gasPrice for legacy transactions.' + ) + } + } + + // Ensure the request and params are aligned for EIP-1559 + const EIP1559RequestMissingGasParams = isEIP1559Transaction && !isEIP1559Params + if (EIP1559RequestMissingGasParams) { + return new Error( + 'Transaction request specifies EIP-1559 gas values, but currentGasFeeParams lacks corresponding EIP-1559 parameters.' + ) + } + + // Ensure the request and params are aligned for Legacy transactions + const legacyRequestMissingGasParams = isLegacyTransaction && !isLegacyParams + if (legacyRequestMissingGasParams) { + return new Error( + 'Transaction request specifies Legacy gasPrice, but currentGasFeeParams lacks a corresponding gasPrice.' + ) + } + + // The transaction does not match expected gas value patterns for Legacy or EIP-1559 transactions. + // Ensure the transaction includes appropriate gas values (gasPrice for Legacy, maxFeePerGas and maxPriorityFeePerGas for 1559).' + return new Error('Unable to determine gas fee structure.') } function multiplyByFactor( diff --git a/packages/wallet/src/features/language/saga.ts b/packages/wallet/src/features/language/saga.ts index 0a0e489de19..ed17f13fc36 100644 --- a/packages/wallet/src/features/language/saga.ts +++ b/packages/wallet/src/features/language/saga.ts @@ -1,8 +1,8 @@ import { I18nManager } from 'react-native' import RNRestart from 'react-native-restart' -import { Statsig } from 'statsig-react-native' import { call, put, select, takeLatest } from 'typed-redux-saga' -import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' +import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags' +import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig' import i18n from 'uniswap/src/i18n/i18n' import { getDeviceLocales } from 'utilities/src/device/locales' import { logger } from 'utilities/src/logger/logger' diff --git a/apps/mobile/src/features/nfts/hooks.ts b/packages/wallet/src/features/nfts/useNftContextMenu.tsx similarity index 97% rename from apps/mobile/src/features/nfts/hooks.ts rename to packages/wallet/src/features/nfts/useNftContextMenu.tsx index 2dacbe8c397..7ebaea3ba63 100644 --- a/apps/mobile/src/features/nfts/hooks.ts +++ b/packages/wallet/src/features/nfts/useNftContextMenu.tsx @@ -2,7 +2,6 @@ import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { NativeSyntheticEvent } from 'react-native' import { ContextMenuAction, ContextMenuOnPressNativeEvent } from 'react-native-context-menu-view' -import { useAppDispatch, useAppSelector } from 'src/app/hooks' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { useWalletNavigation } from 'wallet/src/contexts/WalletNavigationContext' import { selectNftsVisibility } from 'wallet/src/features/favorites/selectors' @@ -11,6 +10,7 @@ import { getNFTAssetKey } from 'wallet/src/features/nfts/utils' import { pushNotification } from 'wallet/src/features/notifications/slice' import { AppNotificationType } from 'wallet/src/features/notifications/types' import { useAccounts } from 'wallet/src/features/wallet/hooks' +import { useAppDispatch, useAppSelector } from 'wallet/src/state' interface NFTMenuParams { tokenId?: string @@ -20,7 +20,7 @@ interface NFTMenuParams { isSpam?: boolean } -export function useNFTMenu({ +export function useNFTContextMenu({ contractAddress, tokenId, owner, diff --git a/packages/wallet/src/features/notifications/components/DappConnectedNotification.tsx b/packages/wallet/src/features/notifications/components/DappConnectedNotification.tsx new file mode 100644 index 00000000000..955bd67b228 --- /dev/null +++ b/packages/wallet/src/features/notifications/components/DappConnectedNotification.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from 'react-i18next' +import { Image } from 'ui/src' +import { iconSizes } from 'ui/src/theme' +import { NotificationToast } from 'wallet/src/features/notifications/components/NotificationToast' +import { DappConnectedNotification } from 'wallet/src/features/notifications/types' + +export function DappConnectedNotification({ + notification: { hideDelay = 2000, dappIconUrl }, +}: { + notification: DappConnectedNotification +}): JSX.Element | null { + const { t } = useTranslation() + + return ( + + ) : undefined + } + title={t('common.text.connected')} + /> + ) +} diff --git a/packages/wallet/src/features/notifications/components/DappDisconnectedNotification.tsx b/packages/wallet/src/features/notifications/components/DappDisconnectedNotification.tsx new file mode 100644 index 00000000000..33557d0bb3c --- /dev/null +++ b/packages/wallet/src/features/notifications/components/DappDisconnectedNotification.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from 'react-i18next' +import { Image } from 'ui/src' +import { iconSizes } from 'ui/src/theme' +import { NotificationToast } from 'wallet/src/features/notifications/components/NotificationToast' +import { DappDisconnectedNotification } from 'wallet/src/features/notifications/types' + +export function DappDisconnectedNotification({ + notification: { hideDelay = 2000, dappIconUrl }, +}: { + notification: DappDisconnectedNotification +}): JSX.Element | null { + const { t } = useTranslation() + + return ( + + ) : undefined + } + title={t('common.text.disconnected')} + /> + ) +} diff --git a/packages/wallet/src/features/notifications/notificationWatcherSaga.ts b/packages/wallet/src/features/notifications/notificationWatcherSaga.ts index d832e600761..f4307f71622 100644 --- a/packages/wallet/src/features/notifications/notificationWatcherSaga.ts +++ b/packages/wallet/src/features/notifications/notificationWatcherSaga.ts @@ -109,7 +109,7 @@ export function* pushTransactionNotification(action: ReturnType chainId?: number } @@ -187,6 +189,14 @@ export interface TransferCurrencyPendingNotification extends AppNotificationBase export interface ScantasticCompleteNotification extends AppNotificationBase { type: AppNotificationType.ScantasticComplete } +export interface DappConnectedNotification extends AppNotificationBase { + type: AppNotificationType.DappConnected + dappIconUrl: Maybe +} +export interface DappDisconnectedNotification extends AppNotificationBase { + type: AppNotificationType.DappDisconnected + dappIconUrl: Maybe +} export type AppNotification = | AppNotificationDefault @@ -202,3 +212,5 @@ export type AppNotification = | ChangeAssetVisibilityNotification | SuccessNotification | ScantasticCompleteNotification + | DappConnectedNotification + | DappDisconnectedNotification diff --git a/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx b/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx index 45a7bfabfbb..42b11851fa5 100644 --- a/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx +++ b/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx @@ -123,19 +123,19 @@ export function useTokenContextMenu({ onPress: navigateToReceive, ...(isWeb ? { - Icon: Icons.QrCode, + Icon: Icons.ReceiveAlt, } : { systemIcon: 'qrcode' }), }, - { - title: t('common.button.share'), - onPress: onPressShare, - ...(isWeb - ? { - Icon: Icons.Share, - } - : { systemIcon: 'square.and.arrow.up' }), - }, + ...(!isWeb + ? [ + { + title: t('common.button.share'), + onPress: onPressShare, + systemIcon: 'square.and.arrow.up', + }, + ] + : []), ...(activeAccountHoldsToken ? [ { diff --git a/packages/wallet/src/features/search/SearchTextInput.tsx b/packages/wallet/src/features/search/SearchTextInput.tsx index c4745f05ab9..6269c6b4f53 100644 --- a/packages/wallet/src/features/search/SearchTextInput.tsx +++ b/packages/wallet/src/features/search/SearchTextInput.tsx @@ -86,6 +86,7 @@ export const SearchTextInput = forwardRef ) const onPressCancel = (): void => { + inputRef.current?.clear() setIsFocus(false) setShowClearButton(false) Keyboard.dismiss() diff --git a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionActionsModal.tsx b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionActionsModal.tsx index 63a24f11d12..de24816e47f 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionActionsModal.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionActionsModal.tsx @@ -1,7 +1,7 @@ import dayjs from 'dayjs' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { ColorTokens, Flex, Separator, Text } from 'ui/src' +import { ColorTokens, Flex, Separator, Text, isWeb } from 'ui/src' import { CurrencyId } from 'uniswap/src/types/currency' import { ActionSheetModalContent, @@ -208,7 +208,8 @@ export default function TransactionActionsModal({ hideHandlebar backgroundColor="statusCritical" name={ModalName.TransactionActions} - onClose={handleClose}> + onClose={handleClose} + {...(isWeb && { alignment: 'top' })}> = { const walletSlice = { accounts, activeAccountAddress: null, - isUnlocked: false, settings: { swapProtection: SwapProtectionSetting.On, hideSmallBalances: true, diff --git a/packages/wallet/src/features/transactions/TransactionReview/TransactionReview.tsx b/packages/wallet/src/features/transactions/TransactionReview/TransactionReview.tsx index 2ed37882e91..ff1e803a603 100644 --- a/packages/wallet/src/features/transactions/TransactionReview/TransactionReview.tsx +++ b/packages/wallet/src/features/transactions/TransactionReview/TransactionReview.tsx @@ -8,12 +8,12 @@ import { fonts, iconSizes } from 'ui/src/theme' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { NumberType } from 'utilities/src/format/types' import { CurrencyLogo } from 'wallet/src/components/CurrencyLogo/CurrencyLogo' -import { NFTTransfer } from 'wallet/src/components/NFT/NFTTransfer' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { TransferArrowButton } from 'wallet/src/components/buttons/TransferArrowButton' import { AmountInput } from 'wallet/src/components/input/AmountInput' import { RecipientPrevTransfers } from 'wallet/src/components/input/RecipientInputPanel' import { TextInputProps } from 'wallet/src/components/input/TextInput' +import { NFTTransfer } from 'wallet/src/components/nfts/NFTTransfer' import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext' import { GQLNftAsset } from 'wallet/src/features/nfts/hooks' import { ElementName, ElementNameType } from 'wallet/src/telemetry/constants' diff --git a/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts b/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts index af51d9e64f0..5d38d843a67 100644 --- a/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts +++ b/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts @@ -17,6 +17,7 @@ function parseFiatPurchaseTransaction( destinationCurrencyCode: outputCurrency, destinationAmount: outputCurrencyAmount, cryptoDetails, + serviceProvider, } = transaction const chainId = toSupportedChainId(cryptoDetails?.chainId) @@ -34,6 +35,7 @@ function parseFiatPurchaseTransaction( // this marks the tx as 'valid' / ready to display in the ui syncedWithBackend: true, chainId, + serviceProvider, } } diff --git a/packages/wallet/src/features/transactions/swap/SwapFlow.tsx b/packages/wallet/src/features/transactions/swap/SwapFlow.tsx index 1549db5c0ac..8ca4db668e2 100644 --- a/packages/wallet/src/features/transactions/swap/SwapFlow.tsx +++ b/packages/wallet/src/features/transactions/swap/SwapFlow.tsx @@ -1,7 +1,7 @@ import { Dispatch, ReactNode, SetStateAction, useEffect, useState } from 'react' import { isWeb } from 'ui/src' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { Trace } from 'utilities/src/telemetry/trace/Trace' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { diff --git a/packages/wallet/src/features/transactions/swap/customRpc.ts b/packages/wallet/src/features/transactions/swap/customRpc.ts index 5e6a0f2b85f..268ca21c21f 100644 --- a/packages/wallet/src/features/transactions/swap/customRpc.ts +++ b/packages/wallet/src/features/transactions/swap/customRpc.ts @@ -1,5 +1,5 @@ -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { ChainId } from 'wallet/src/constants/chains' import { isPrivateRpcSupportedOnChain } from 'wallet/src/features/providers' import { useSwapProtectionSetting } from 'wallet/src/features/wallet/hooks' diff --git a/packages/wallet/src/features/transactions/swap/modals/settings/SwapSettingsModal.tsx b/packages/wallet/src/features/transactions/swap/modals/settings/SwapSettingsModal.tsx index 5df29a69e5d..6c48b4fd546 100644 --- a/packages/wallet/src/features/transactions/swap/modals/settings/SwapSettingsModal.tsx +++ b/packages/wallet/src/features/transactions/swap/modals/settings/SwapSettingsModal.tsx @@ -2,8 +2,8 @@ import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { Button, Flex, Icons, Text, TouchableArea, isWeb, useSporeColors } from 'ui/src' import { iconSizes } from 'ui/src/theme' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { Switch, WebSwitch } from 'wallet/src/components/buttons/Switch' import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal' import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains' diff --git a/packages/wallet/src/features/transactions/swap/swapSaga.ts b/packages/wallet/src/features/transactions/swap/swapSaga.ts index bddbb9b37dc..6027e957db6 100644 --- a/packages/wallet/src/features/transactions/swap/swapSaga.ts +++ b/packages/wallet/src/features/transactions/swap/swapSaga.ts @@ -1,7 +1,7 @@ import { providers } from 'ethers' -import { Statsig } from 'statsig-react-native' import { call, select } from 'typed-redux-saga' -import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' +import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags' +import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig' import { logger } from 'utilities/src/logger/logger' import { RPCType } from 'wallet/src/constants/chains' import { isPrivateRpcSupportedOnChain } from 'wallet/src/features/providers' diff --git a/packages/wallet/src/features/transactions/swap/trade/hooks/useDerivedSwapInfo.ts b/packages/wallet/src/features/transactions/swap/trade/hooks/useDerivedSwapInfo.ts index 89af7e7f7e8..8fa27e63b2d 100644 --- a/packages/wallet/src/features/transactions/swap/trade/hooks/useDerivedSwapInfo.ts +++ b/packages/wallet/src/features/transactions/swap/trade/hooks/useDerivedSwapInfo.ts @@ -1,7 +1,7 @@ import { TradeType } from '@uniswap/sdk-core' import { useMemo } from 'react' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { ChainId } from 'wallet/src/constants/chains' import { useOnChainCurrencyBalance } from 'wallet/src/features/portfolio/api' import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo' diff --git a/packages/wallet/src/features/transactions/swap/trade/hooks/useSetTradeSlippage.ts b/packages/wallet/src/features/transactions/swap/trade/hooks/useSetTradeSlippage.ts index ba102f8ee40..b501792041b 100644 --- a/packages/wallet/src/features/transactions/swap/trade/hooks/useSetTradeSlippage.ts +++ b/packages/wallet/src/features/transactions/swap/trade/hooks/useSetTradeSlippage.ts @@ -1,4 +1,6 @@ import { useMemo } from 'react' +import { DynamicConfigs } from 'uniswap/src/features/statsig/configs' +import { useDynamicConfig } from 'uniswap/src/features/statsig/hooks' import { MAX_AUTO_SLIPPAGE_TOLERANCE, MIN_AUTO_SLIPPAGE_TOLERANCE, @@ -83,12 +85,21 @@ function useCalculateAutoSlippage(trade: Maybe): number { const outputAmountUSD = useUSDCValue(trade?.outputAmount)?.toExact() + const minAutoSlippageToleranceL2 = useSlippageValueFromDynamicConfig( + SlippageConfigName.MinAutoSlippageToleranceL2 + ) + return useMemo(() => { if (isLegacyQuote) { const chainId = trade.quoteData.quote?.route[0]?.[0]?.tokenIn?.chainId const onL2 = isL2Chain(chainId) const gasCostUSD = trade.quoteData.quote?.gasUseEstimateUSD - return calculateAutoSlippage({ onL2, gasCostUSD, outputAmountUSD }) + return calculateAutoSlippage({ + onL2, + minAutoSlippageToleranceL2, + gasCostUSD, + outputAmountUSD, + }) } else { // TODO:api remove this during Uniswap X integration const quote = isClassicQuote(trade?.quoteData?.quote?.quote) @@ -97,21 +108,32 @@ function useCalculateAutoSlippage(trade: Maybe): number { const chainId = toSupportedChainId(quote?.chainId) ?? undefined const onL2 = isL2Chain(chainId) const gasCostUSD = quote?.gasFeeUSD - return calculateAutoSlippage({ onL2, gasCostUSD, outputAmountUSD }) + return calculateAutoSlippage({ + onL2, + minAutoSlippageToleranceL2, + gasCostUSD, + outputAmountUSD, + }) } - }, [isLegacyQuote, outputAmountUSD, trade]) + }, [isLegacyQuote, minAutoSlippageToleranceL2, outputAmountUSD, trade]) } function calculateAutoSlippage({ onL2, + minAutoSlippageToleranceL2, gasCostUSD, outputAmountUSD, }: { onL2: boolean + minAutoSlippageToleranceL2: number gasCostUSD?: string outputAmountUSD?: string }): number { - if (onL2 || !gasCostUSD || !outputAmountUSD) { + if (onL2) { + return minAutoSlippageToleranceL2 + } + + if (!gasCostUSD || !outputAmountUSD) { return MIN_AUTO_SLIPPAGE_TOLERANCE } @@ -127,3 +149,22 @@ function calculateAutoSlippage({ return Number(suggestedSlippageTolerance.toFixed(2)) } + +enum SlippageConfigName { + MinAutoSlippageToleranceL2, +} + +// Allows us to type the values stored in the JSON dynamic config object for slippage params. +// Names in mapping should exatcly match the JSON object in statsig. +export const SLIPPAGE_CONFIG_NAMES = new Map([ + [SlippageConfigName.MinAutoSlippageToleranceL2, 'minAutoSlippageToleranceL2'], +]) + +function useSlippageValueFromDynamicConfig(configName: SlippageConfigName): number { + const slippageConfig = useDynamicConfig(DynamicConfigs.Slippage) + + const slippageValue = slippageConfig.getValue(SLIPPAGE_CONFIG_NAMES.get(configName)) as string + + // Format as % number + return parseInt(slippageValue, 10) +} diff --git a/packages/wallet/src/features/transactions/transactionState/transactionState.test.ts b/packages/wallet/src/features/transactions/transactionState/transactionState.test.ts index 36c0ddad99c..1787d139ca3 100644 --- a/packages/wallet/src/features/transactions/transactionState/transactionState.test.ts +++ b/packages/wallet/src/features/transactions/transactionState/transactionState.test.ts @@ -7,7 +7,7 @@ import { TransactionState, } from 'wallet/src/features/transactions/transactionState/types' import { - initialState, + INITIAL_TRANSACTION_STATE, selectCurrency, switchCurrencySides, transactionStateReducer, @@ -33,7 +33,7 @@ const testInitialState: Readonly = { } test('should return the initial state', () => { - expect(transactionStateReducer(undefined, {} as AnyAction)).toEqual(initialState) + expect(transactionStateReducer(undefined, {} as AnyAction)).toEqual(INITIAL_TRANSACTION_STATE) }) describe(selectCurrency, () => { diff --git a/packages/wallet/src/features/transactions/transactionState/transactionState.ts b/packages/wallet/src/features/transactions/transactionState/transactionState.ts index 4240ff4b4c3..00ebd2d3cf8 100644 --- a/packages/wallet/src/features/transactions/transactionState/transactionState.ts +++ b/packages/wallet/src/features/transactions/transactionState/transactionState.ts @@ -14,8 +14,7 @@ const ETH_TRADEABLE_ASSET: TradeableAsset = { type: AssetType.Currency, } -// instead of defaulting to mainnet eth -export const initialState: Readonly = { +export const INITIAL_TRANSACTION_STATE: Readonly = { [CurrencyField.INPUT]: ETH_TRADEABLE_ASSET, [CurrencyField.OUTPUT]: null, exactCurrencyField: CurrencyField.INPUT, @@ -31,7 +30,7 @@ export const initialState: Readonly = { // using `createSlice` for convenience -- slice is not added to root reducer const slice = createSlice({ name: 'TransactionState', - initialState, + initialState: INITIAL_TRANSACTION_STATE, reducers: { /** * Sets currency at `field` to the given currency diff --git a/packages/wallet/src/features/transactions/transactionWatcherSaga.ts b/packages/wallet/src/features/transactions/transactionWatcherSaga.ts index 04c17332b62..20df93146d3 100644 --- a/packages/wallet/src/features/transactions/transactionWatcherSaga.ts +++ b/packages/wallet/src/features/transactions/transactionWatcherSaga.ts @@ -2,9 +2,9 @@ import { ApolloClient, NormalizedCacheObject } from '@apollo/client' import { SwapEventName } from '@uniswap/analytics-events' import { TradeType } from '@uniswap/sdk-core' import { BigNumberish, providers } from 'ethers' -import { Statsig } from 'statsig-react-native' import { call, delay, fork, put, race, select, take } from 'typed-redux-saga' -import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/experiments/flags' +import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/statsig/flags' +import { Statsig } from 'uniswap/src/features/statsig/sdk/statsig' import i18n from 'uniswap/src/i18n/i18n' import { logger } from 'utilities/src/logger/logger' import { ChainId } from 'wallet/src/constants/chains' @@ -138,6 +138,9 @@ export function* watchFiatOnRampTransaction(transaction: FiatOnRampTransactionDe useOldMoonpayIntegration ) + let latestStatus = transaction.status + let syncWithBackend = transaction.typeInfo.syncedWithBackend + try { while (true) { const updatedTransaction = yield* useOldMoonpayIntegration @@ -149,12 +152,16 @@ export function* watchFiatOnRampTransaction(transaction: FiatOnRampTransactionDe return } // Transaction has been updated - if (JSON.stringify(updatedTransaction) !== JSON.stringify(transaction)) { + if ( + latestStatus !== updatedTransaction.status || + (!syncWithBackend && updatedTransaction.typeInfo.syncedWithBackend) + ) { logger.debug( 'transactionWatcherSaga', 'watchFiatOnRampTransaction', `Updating transaction with id ${id} from status ${transaction.status} to ${updatedTransaction.status}` ) + const isTransfer = updatedTransaction.typeInfo.inputSymbol === updatedTransaction.typeInfo.outputSymbol if (isTransfer && transaction.typeInfo.institution) { @@ -174,25 +181,28 @@ export function* watchFiatOnRampTransaction(transaction: FiatOnRampTransactionDe serviceProvider: transaction.typeInfo.serviceProvider, }) } + } - // Stale transaction - if (updatedTransaction.status === TransactionStatus.Unknown) { - yield* call(deleteTransaction, updatedTransaction) - break // stop polling - } + latestStatus = updatedTransaction.status + syncWithBackend = updatedTransaction.typeInfo.syncedWithBackend - // Update transaction - yield* put(upsertFiatOnRampTransaction(updatedTransaction)) - - // Finished transaction - if ( - updatedTransaction.status === TransactionStatus.Failed || - updatedTransaction.status === TransactionStatus.Success - ) { - // Show notification badge - yield* put(setNotificationStatus({ address: transaction.from, hasNotifications: true })) - break // stop polling - } + // Stale transaction + if (updatedTransaction.status === TransactionStatus.Unknown) { + yield* call(deleteTransaction, updatedTransaction) + break // stop polling + } + + // Update transaction + yield* put(upsertFiatOnRampTransaction(updatedTransaction)) + + // Finished transaction + if ( + updatedTransaction.status === TransactionStatus.Failed || + updatedTransaction.status === TransactionStatus.Success + ) { + // Show notification badge + yield* put(setNotificationStatus({ address: transaction.from, hasNotifications: true })) + break // stop polling } // at this point, we received a response from backend diff --git a/packages/wallet/src/features/transactions/transfer/TransferTokenForm.tsx b/packages/wallet/src/features/transactions/transfer/TransferTokenForm.tsx index 80ab97d1b1f..04ecb05cb86 100644 --- a/packages/wallet/src/features/transactions/transfer/TransferTokenForm.tsx +++ b/packages/wallet/src/features/transactions/transfer/TransferTokenForm.tsx @@ -19,13 +19,13 @@ import { import InfoCircleFilled from 'ui/src/assets/icons/info-circle-filled.svg' import { iconSizes, spacing } from 'ui/src/theme' import { usePrevious } from 'utilities/src/react/hooks' -import { NFTTransfer } from 'wallet/src/components/NFT/NFTTransfer' import { TransferArrowButton } from 'wallet/src/components/buttons/TransferArrowButton' import { RecipientInputPanel } from 'wallet/src/components/input/RecipientInputPanel' import { TextInputProps } from 'wallet/src/components/input/TextInput' import { CurrencyInputPanelLegacy } from 'wallet/src/components/legacy/CurrencyInputPanelLegacy' import { DecimalPadLegacy } from 'wallet/src/components/legacy/DecimalPadLegacy' import { WarningModal, getAlertColor } from 'wallet/src/components/modals/WarningModal/WarningModal' +import { NFTTransfer } from 'wallet/src/components/nfts/NFTTransfer' import { WarningAction, WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types' import { ParsedWarnings } from 'wallet/src/features/transactions/hooks/useParsedTransactionWarnings' import { useTokenFormActionHandlers } from 'wallet/src/features/transactions/hooks/useTokenFormActionHandlers' diff --git a/packages/wallet/src/features/transactions/transfer/getSendPrefilledState.ts b/packages/wallet/src/features/transactions/transfer/getSendPrefilledState.ts new file mode 100644 index 00000000000..608e193d911 --- /dev/null +++ b/packages/wallet/src/features/transactions/transfer/getSendPrefilledState.ts @@ -0,0 +1,41 @@ +import { getNativeAddress } from 'wallet/src/constants/addresses' +import { ChainId } from 'wallet/src/constants/chains' +import { AssetType, CurrencyAsset } from 'wallet/src/entities/assets' +import { + CurrencyField, + TransactionState, +} from 'wallet/src/features/transactions/transactionState/types' + +export function getSendPrefilledState({ + chainId, + currencyAddress, +}: { + chainId: ChainId + currencyAddress?: Address +}): TransactionState { + const nativeTokenAddress = getNativeAddress(chainId) + + const nativeToken: CurrencyAsset = { + address: nativeTokenAddress, + chainId, + type: AssetType.Currency, + } + + const chosenToken: CurrencyAsset | undefined = !currencyAddress + ? undefined + : { + address: currencyAddress, + chainId, + type: AssetType.Currency, + } + + const transactionState: TransactionState = { + exactCurrencyField: CurrencyField.INPUT, + exactAmountToken: '', + // If specified currency address populate the currency, otherwise default to native token on chain + [CurrencyField.INPUT]: chosenToken ?? nativeToken, + [CurrencyField.OUTPUT]: null, + } + + return transactionState +} diff --git a/packages/wallet/src/features/transactions/transfer/hooks/useTransferTransactionRequest.ts b/packages/wallet/src/features/transactions/transfer/hooks/useTransferTransactionRequest.ts index df6e44bc4e5..58eb65a96ba 100644 --- a/packages/wallet/src/features/transactions/transfer/hooks/useTransferTransactionRequest.ts +++ b/packages/wallet/src/features/transactions/transfer/hooks/useTransferTransactionRequest.ts @@ -178,7 +178,7 @@ function getNativeTransferRequest(params: TransferCurrencyParams): providers.Tra } } -async function getTokenTransferRequest( +export async function getTokenTransferRequest( params: TransferCurrencyParams, provider: providers.Provider, contractManager: ContractManager diff --git a/packages/wallet/src/features/unitags/hooks.ts b/packages/wallet/src/features/unitags/hooks.ts index 58ec25246b0..ac8edd1746b 100644 --- a/packages/wallet/src/features/unitags/hooks.ts +++ b/packages/wallet/src/features/unitags/hooks.ts @@ -2,8 +2,8 @@ import { TFunction } from 'i18next' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { getUniqueId } from 'react-native-device-info' -import { FeatureFlags } from 'uniswap/src/features/experiments/flags' -import { useFeatureFlag } from 'uniswap/src/features/experiments/hooks' +import { FeatureFlags } from 'uniswap/src/features/statsig/flags' +import { useFeatureFlag } from 'uniswap/src/features/statsig/hooks' import { useUnitagQuery, useWaitlistPositionQuery } from 'uniswap/src/features/unitags/api' import { useUnitagUpdater } from 'uniswap/src/features/unitags/context' import { diff --git a/packages/wallet/src/features/wallet/import/importAccountSaga.test.ts b/packages/wallet/src/features/wallet/import/importAccountSaga.test.ts index 18576f30e11..f2fc4e5f4c0 100644 --- a/packages/wallet/src/features/wallet/import/importAccountSaga.test.ts +++ b/packages/wallet/src/features/wallet/import/importAccountSaga.test.ts @@ -5,7 +5,6 @@ import { addAccounts, restoreMnemonicComplete, setAccountAsActive, - unlockWallet, } from 'wallet/src/features/wallet/slice' import { SAMPLE_PASSWORD, @@ -75,7 +74,6 @@ describe(importAccount, () => { }, ]), setAccountAsActive(SAMPLE_SEED_ADDRESS_1), - unlockWallet(), ]) }) @@ -154,7 +152,6 @@ describe(importAccount, () => { }, ]), setAccountAsActive(SAMPLE_SEED_ADDRESS_1), - unlockWallet(), ]) }) }) diff --git a/packages/wallet/src/features/wallet/import/importAccountSaga.ts b/packages/wallet/src/features/wallet/import/importAccountSaga.ts index aeff3d5074e..35119c2ee5c 100644 --- a/packages/wallet/src/features/wallet/import/importAccountSaga.ts +++ b/packages/wallet/src/features/wallet/import/importAccountSaga.ts @@ -9,7 +9,6 @@ import { addAccounts, restoreMnemonicComplete, setAccountAsActive, - unlockWallet, } from 'wallet/src/features/wallet/slice' import { getValidAddress } from 'wallet/src/utils/addresses' import { createMonitoredSaga } from 'wallet/src/utils/saga' @@ -190,7 +189,6 @@ function* onAccountImport(account: Account, ignoreActivate?: boolean) { if (!ignoreActivate) { yield* put(setAccountAsActive(account.address)) } - yield* put(unlockWallet()) logger.debug('importAccount', '', `New ${account.type} account imported: ${account.address}`) } diff --git a/packages/wallet/src/features/wallet/slice.test.ts b/packages/wallet/src/features/wallet/slice.test.ts index f6dd78ac998..82919f7ad7a 100644 --- a/packages/wallet/src/features/wallet/slice.test.ts +++ b/packages/wallet/src/features/wallet/slice.test.ts @@ -32,7 +32,6 @@ describe(walletReducer, () => { store = createStore(walletReducer, { accounts: {}, activeAccountAddress: null, - isUnlocked: false, settings: { swapProtection: SwapProtectionSetting.On, hideSmallBalances: true, diff --git a/packages/wallet/src/features/wallet/slice.ts b/packages/wallet/src/features/wallet/slice.ts index 4bb9a33cec9..7ad7407c30a 100644 --- a/packages/wallet/src/features/wallet/slice.ts +++ b/packages/wallet/src/features/wallet/slice.ts @@ -14,7 +14,6 @@ export interface WalletState { accounts: Record activeAccountAddress: Address | null finishedOnboarding?: boolean - isUnlocked: boolean // Persisted UI configs set by the user through interaction with filters and settings settings: { nftViewType?: NFTViewType @@ -36,7 +35,6 @@ export interface WalletState { export const initialWalletState: WalletState = { accounts: {}, activeAccountAddress: null, - isUnlocked: false, settings: { swapProtection: SwapProtectionSetting.On, hideSmallBalances: true, @@ -134,12 +132,6 @@ const slice = createSlice({ } state.activeAccountAddress = id }, - unlockWallet: (state) => { - state.isUnlocked = true - }, - lockWallet: (state) => { - state.isUnlocked = false - }, setFinishedOnboarding: ( state, { payload: { finishedOnboarding } }: PayloadAction<{ finishedOnboarding: boolean }> @@ -197,8 +189,6 @@ export const { setAccountsNonPending, editAccount, setAccountAsActive, - unlockWallet, - lockWallet, resetWallet, setFinishedOnboarding, setNFTViewType, diff --git a/packages/wallet/src/features/walletConnect/types.ts b/packages/wallet/src/features/walletConnect/types.ts index 3a0d269abe3..ae0580ebda2 100644 --- a/packages/wallet/src/features/walletConnect/types.ts +++ b/packages/wallet/src/features/walletConnect/types.ts @@ -17,6 +17,10 @@ export enum EthMethod { PersonalSign = 'personal_sign', } +export enum UwULinkMethod { + Erc20Send = 'erc20_send', +} + export enum EthEvent { AccountsChanged = 'accountsChanged', ChainChanged = 'chainChanged', @@ -44,18 +48,35 @@ export type EthSignMethod = | EthMethod.SignTypedDataV4 interface UwULinkRequestDappInfo { - name: string - url: string - icon: string + name?: string + url?: string + icon?: string } -export interface UwULinkRequest { - method: EthMethod.EthSendTransaction - value: EthTransaction +interface UwULinkBaseRequest { + method: EthMethod.EthSendTransaction | UwULinkMethod.Erc20Send chainId: number - dapp: UwULinkRequestDappInfo + dapp?: UwULinkRequestDappInfo webhook?: string } + +interface UwULinkGenericTransactionRequest extends UwULinkBaseRequest { + method: EthMethod.EthSendTransaction + value: EthTransaction +} + +export interface UwULinkErc20SendRequest extends UwULinkBaseRequest { + method: UwULinkMethod.Erc20Send + recipient: string + tokenAddress: string + amount: string + + // TODO: the wallet should determine stablecoin status + isStablecoin: false +} + +export type UwULinkRequest = UwULinkGenericTransactionRequest | UwULinkErc20SendRequest + export interface DappInfoWC { source: 'walletconnect' name: string @@ -67,7 +88,7 @@ export interface DappInfoUwULink { source: 'uwulink' name: string url: string - icon: string + icon?: string chain_id: number webhook?: string } diff --git a/packages/wallet/src/telemetry/constants.ts b/packages/wallet/src/telemetry/constants.ts index 97816ae427c..34853589ae3 100644 --- a/packages/wallet/src/telemetry/constants.ts +++ b/packages/wallet/src/telemetry/constants.ts @@ -103,6 +103,7 @@ export const ModalName = { UnitagsIntro: 'unitags-intro-modal', UniconsV2: 'unicons-v2-intro-modal', UniconsDevModal: 'unicons-dev-modal', + UwULinkErc20SendModal: 'uwulink-erc20-send-modal', ViewSeedPhraseWarning: 'view-seed-phrase-warning', ViewOnlyExplainer: 'view-only-explainer-modal', WalletConnectScan: 'wallet-connect-scan-modal', diff --git a/packages/wallet/src/telemetry/types.ts b/packages/wallet/src/telemetry/types.ts index 2aa23465734..2297e634888 100644 --- a/packages/wallet/src/telemetry/types.ts +++ b/packages/wallet/src/telemetry/types.ts @@ -96,12 +96,12 @@ export type WalletEventProperties = { serviceProvider: string } [FiatOnRampEventName.FiatOnRampWidgetOpened]: TraceProps & { - countryCode: string + countryCode?: string countryState?: string cryptoCurrency: string externalTransactionId: string fiatCurrency: string - preselectedServiceProvider: string + preselectedServiceProvider?: string serviceProvider: string } [SharedEventName.ANALYTICS_SWITCH_TOGGLED]: { diff --git a/packages/wallet/src/utils/persistedStorage.ts b/packages/wallet/src/utils/persistedStorage.ts index 142ae0ce4da..b57f786f0b7 100644 --- a/packages/wallet/src/utils/persistedStorage.ts +++ b/packages/wallet/src/utils/persistedStorage.ts @@ -1,7 +1,7 @@ type AreaName = keyof Pick export const prefix = 'com.uniswap.web' -export const encryptionKeyKey = `${prefix}.encryptionKey` +export const ENCRYPTION_KEY_STORAGE_KEY = `${prefix}.encryptionKey` /** * Chrome storage wrapper diff --git a/scripts/check-modified-files.sh b/scripts/check-modified-files.sh index 5fde1074b8c..51565dfff11 100755 --- a/scripts/check-modified-files.sh +++ b/scripts/check-modified-files.sh @@ -6,8 +6,8 @@ echo "${DIFF}" # Fail if working tree has changes if [ "$DIFF" ]; then - echo "This branch has not updated our generated strings/translations file. Please run yarn i18n:extract" + echo "This step has modified files when it should not have!" exit 1; fi -exit 0; \ No newline at end of file +exit 0; diff --git a/yarn.lock b/yarn.lock index e43bb561de7..02dfa9dfd24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,10 +62,10 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:1.9.4": - version: 1.9.4 - resolution: "@adraffy/ens-normalize@npm:1.9.4" - checksum: 7d7fff58ebe2c4961f7e5e61dad123aa6a63fec0df5c84af1fa41079dc05d398599690be4427b3a94d2baa94084544bcfdf2d51cbed7504b9b0583b0960ad550 +"@adraffy/ens-normalize@npm:1.10.0": + version: 1.10.0 + resolution: "@adraffy/ens-normalize@npm:1.10.0" + checksum: af0540f963a2632da2bbc37e36ea6593dcfc607b937857133791781e246d47f870d5e3d21fa70d5cfe94e772c284588c81ea3f5b7f4ea8fbb824369444e4dbcb languageName: node linkType: hard @@ -549,12 +549,12 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.14.5, @babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.22.5": - version: 7.22.15 - resolution: "@babel/helper-module-imports@npm:7.22.15" +"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.14.5, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.22.5": + version: 7.24.3 + resolution: "@babel/helper-module-imports@npm:7.24.3" dependencies: - "@babel/types": ^7.22.15 - checksum: ecd7e457df0a46f889228f943ef9b4a47d485d82e030676767e6a2fdcbdaa63594d8124d4b55fd160b41c201025aec01fc27580352b1c87a37c9c6f33d116702 + "@babel/types": ^7.24.0 + checksum: c23492189ba97a1ec7d37012336a5661174e8b88194836b6bbf90d13c3b72c1db4626263c654454986f924c6da8be7ba7f9447876d709cd00bd6ffde6ec00796 languageName: node linkType: hard @@ -2097,12 +2097,12 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.7, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": - version: 7.24.0 - resolution: "@babel/runtime@npm:7.24.0" +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.7, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": + version: 7.24.4 + resolution: "@babel/runtime@npm:7.24.4" dependencies: regenerator-runtime: ^0.14.0 - checksum: 7a6a5d40fbdd68491ec183ba2e631c07415119960083b4fd76564cce3751e9acd2f12ab89575e38496fa389fa06d458732776e69ee1858e366cc3fbdb049f847 + checksum: 2f27d4c0ffac7ae7999ac0385e1106f2a06992a8bdcbf3da06adcac7413863cd08c198c2e4e970041bbea849e17f02e1df18875539b6afba76c781b6b59a07c3 languageName: node linkType: hard @@ -2135,14 +2135,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.1.6, @babel/types@npm:^7.12.6, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.23.9 - resolution: "@babel/types@npm:7.23.9" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.1.6, @babel/types@npm:^7.12.6, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.24.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.24.0 + resolution: "@babel/types@npm:7.24.0" dependencies: "@babel/helper-string-parser": ^7.23.4 "@babel/helper-validator-identifier": ^7.22.20 to-fast-properties: ^2.0.0 - checksum: 0a9b008e9bfc89beb8c185e620fa0f8ed6c771f1e1b2e01e1596870969096fec7793898a1d64a035176abf1dd13e2668ee30bf699f2d92c210a8128f4b151e65 + checksum: 4b574a37d490f621470ff36a5afaac6deca5546edcb9b5e316d39acbb20998e9c2be42f3fc0bf2b55906fc49ff2a5a6a097e8f5a726ee3f708a0b0ca93aed807 languageName: node linkType: hard @@ -2296,6 +2296,23 @@ __metadata: languageName: node linkType: hard +"@coinbase/wallet-sdk@npm:3.9.1": + version: 3.9.1 + resolution: "@coinbase/wallet-sdk@npm:3.9.1" + dependencies: + bn.js: ^5.2.1 + buffer: ^6.0.3 + clsx: ^1.2.1 + eth-block-tracker: ^7.1.0 + eth-json-rpc-filters: ^6.0.0 + eventemitter3: ^5.0.1 + keccak: ^3.0.3 + preact: ^10.16.0 + sha.js: ^2.4.11 + checksum: 8e6ab9c1fdfe87c703e65e046c62b5d24821b103ae616646dd79b5639a6fef8861e5548a501598bd21d3b6884cd2ed86821b4517c1d3b90574f23f4ca4a459ba + languageName: node + linkType: hard + "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -2797,6 +2814,25 @@ __metadata: languageName: node linkType: hard +"@emotion/babel-plugin@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/babel-plugin@npm:11.11.0" + dependencies: + "@babel/helper-module-imports": ^7.16.7 + "@babel/runtime": ^7.18.3 + "@emotion/hash": ^0.9.1 + "@emotion/memoize": ^0.8.1 + "@emotion/serialize": ^1.1.2 + babel-plugin-macros: ^3.1.0 + convert-source-map: ^1.5.0 + escape-string-regexp: ^4.0.0 + find-root: ^1.1.0 + source-map: ^0.5.7 + stylis: 4.2.0 + checksum: 6b363edccc10290f7a23242c06f88e451b5feb2ab94152b18bb8883033db5934fb0e421e2d67d09907c13837c21218a3ac28c51707778a54d6cd3706c0c2f3f9 + languageName: node + linkType: hard + "@emotion/cache@npm:^10.0.27": version: 10.0.29 resolution: "@emotion/cache@npm:10.0.29" @@ -2809,6 +2845,19 @@ __metadata: languageName: node linkType: hard +"@emotion/cache@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/cache@npm:11.11.0" + dependencies: + "@emotion/memoize": ^0.8.1 + "@emotion/sheet": ^1.2.2 + "@emotion/utils": ^1.2.1 + "@emotion/weak-memoize": ^0.3.1 + stylis: 4.2.0 + checksum: 8eb1dc22beaa20c21a2e04c284d5a2630a018a9d51fb190e52de348c8d27f4e8ca4bbab003d68b4f6cd9cc1c569ca747a997797e0f76d6c734a660dc29decf08 + languageName: node + linkType: hard + "@emotion/core@npm:^10.0.0": version: 10.3.1 resolution: "@emotion/core@npm:10.3.1" @@ -2843,7 +2892,7 @@ __metadata: languageName: node linkType: hard -"@emotion/hash@npm:^0.9.0": +"@emotion/hash@npm:^0.9.0, @emotion/hash@npm:^0.9.1": version: 0.9.1 resolution: "@emotion/hash@npm:0.9.1" checksum: 716e17e48bf9047bf9383982c071de49f2615310fb4e986738931776f5a823bc1f29c84501abe0d3df91a3803c80122d24e28b57351bca9e01356ebb33d89876 @@ -2859,12 +2908,12 @@ __metadata: languageName: node linkType: hard -"@emotion/is-prop-valid@npm:^1.1.0": - version: 1.2.1 - resolution: "@emotion/is-prop-valid@npm:1.2.1" +"@emotion/is-prop-valid@npm:^1.1.0, @emotion/is-prop-valid@npm:^1.2.2": + version: 1.2.2 + resolution: "@emotion/is-prop-valid@npm:1.2.2" dependencies: "@emotion/memoize": ^0.8.1 - checksum: 8f42dc573a3fad79b021479becb639b8fe3b60bdd1081a775d32388bca418ee53074c7602a4c845c5f75fa6831eb1cbdc4d208cc0299f57014ed3a02abcad16a + checksum: 61f6b128ea62b9f76b47955057d5d86fcbe2a6989d2cd1e583daac592901a950475a37d049b9f7a7c6aa8758a33b408735db759fdedfd1f629df0f85ab60ea25 languageName: node linkType: hard @@ -2889,6 +2938,27 @@ __metadata: languageName: node linkType: hard +"@emotion/react@npm:^11.10.6": + version: 11.11.4 + resolution: "@emotion/react@npm:11.11.4" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.11.0 + "@emotion/cache": ^11.11.0 + "@emotion/serialize": ^1.1.3 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.1 + "@emotion/utils": ^1.2.1 + "@emotion/weak-memoize": ^0.3.1 + hoist-non-react-statics: ^3.3.1 + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 6abaa7a05c5e1db31bffca7ac79169f5456990022cbb3794e6903221536609a60420f2b4888dd3f84e9634a304e394130cb88dc32c243a1dedc263e50da329f8 + languageName: node + linkType: hard + "@emotion/serialize@npm:^0.11.15, @emotion/serialize@npm:^0.11.16": version: 0.11.16 resolution: "@emotion/serialize@npm:0.11.16" @@ -2902,6 +2972,19 @@ __metadata: languageName: node linkType: hard +"@emotion/serialize@npm:^1.1.2, @emotion/serialize@npm:^1.1.3, @emotion/serialize@npm:^1.1.4": + version: 1.1.4 + resolution: "@emotion/serialize@npm:1.1.4" + dependencies: + "@emotion/hash": ^0.9.1 + "@emotion/memoize": ^0.8.1 + "@emotion/unitless": ^0.8.1 + "@emotion/utils": ^1.2.1 + csstype: ^3.0.2 + checksum: 71b99f816a9c1d61a87c62cf4928da3894bb62213f3aff38b1ea9790b3368f084af98a3e5453b5055c2f36a7d70318d2fa9955b7b5676c2065b868062375df39 + languageName: node + linkType: hard + "@emotion/sheet@npm:0.9.4": version: 0.9.4 resolution: "@emotion/sheet@npm:0.9.4" @@ -2909,6 +2992,13 @@ __metadata: languageName: node linkType: hard +"@emotion/sheet@npm:^1.2.2": + version: 1.2.2 + resolution: "@emotion/sheet@npm:1.2.2" + checksum: d973273c9c15f1c291ca2269728bf044bd3e92a67bca87943fa9ec6c3cd2b034f9a6bfe95ef1b5d983351d128c75b547b43ff196a00a3875f7e1d269793cecfe + languageName: node + linkType: hard + "@emotion/styled-base@npm:^10.3.0": version: 10.3.0 resolution: "@emotion/styled-base@npm:10.3.0" @@ -2937,6 +3027,26 @@ __metadata: languageName: node linkType: hard +"@emotion/styled@npm:^11.10.6": + version: 11.11.5 + resolution: "@emotion/styled@npm:11.11.5" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.11.0 + "@emotion/is-prop-valid": ^1.2.2 + "@emotion/serialize": ^1.1.4 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.1 + "@emotion/utils": ^1.2.1 + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: ad5fc42d00e8aa9597f6d9665986036d5ebe0e8f8155af6d95831c5e8fb2319fb837724e6c5cd59e5346f14c3263711b7ce7271d34688e974d1f32ffeecb37ba + languageName: node + linkType: hard + "@emotion/stylis@npm:0.8.5, @emotion/stylis@npm:^0.8.4": version: 0.8.5 resolution: "@emotion/stylis@npm:0.8.5" @@ -2951,6 +3061,22 @@ __metadata: languageName: node linkType: hard +"@emotion/unitless@npm:^0.8.1": + version: 0.8.1 + resolution: "@emotion/unitless@npm:0.8.1" + checksum: 385e21d184d27853bb350999471f00e1429fa4e83182f46cd2c164985999d9b46d558dc8b9cc89975cb337831ce50c31ac2f33b15502e85c299892e67e7b4a88 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.1": + version: 1.0.1 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.1" + peerDependencies: + react: ">=16.8.0" + checksum: 700b6e5bbb37a9231f203bb3af11295eed01d73b2293abece0bc2a2237015e944d7b5114d4887ad9a79776504aa51ed2a8b0ddbc117c54495dd01a6b22f93786 + languageName: node + linkType: hard + "@emotion/utils@npm:0.11.3": version: 0.11.3 resolution: "@emotion/utils@npm:0.11.3" @@ -2958,6 +3084,13 @@ __metadata: languageName: node linkType: hard +"@emotion/utils@npm:^1.2.1": + version: 1.2.1 + resolution: "@emotion/utils@npm:1.2.1" + checksum: e0b44be0705b56b079c55faff93952150be69e79b660ae70ddd5b6e09fc40eb1319654315a9f34bb479d7f4ec94be6068c061abbb9e18b9778ae180ad5d97c73 + languageName: node + linkType: hard + "@emotion/weak-memoize@npm:0.2.5": version: 0.2.5 resolution: "@emotion/weak-memoize@npm:0.2.5" @@ -2965,6 +3098,13 @@ __metadata: languageName: node linkType: hard +"@emotion/weak-memoize@npm:^0.3.1": + version: 0.3.1 + resolution: "@emotion/weak-memoize@npm:0.3.1" + checksum: b2be47caa24a8122622ea18cd2d650dbb4f8ad37b636dc41ed420c2e082f7f1e564ecdea68122b546df7f305b159bf5ab9ffee872abd0f052e687428459af594 + languageName: node + linkType: hard + "@esbuild-plugins/node-globals-polyfill@npm:^0.2.3": version: 0.2.3 resolution: "@esbuild-plugins/node-globals-polyfill@npm:0.2.3" @@ -3189,6 +3329,16 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/common@npm:^3.2.0": + version: 3.2.0 + resolution: "@ethereumjs/common@npm:3.2.0" + dependencies: + "@ethereumjs/util": ^8.1.0 + crc-32: ^1.2.0 + checksum: cb9cc11f5c868cb577ba611cebf55046e509218bbb89b47ccce010776dafe8256d70f8f43fab238aec74cf71f62601cd5842bc03a83261200802de365732a14b + languageName: node + linkType: hard + "@ethereumjs/rlp@npm:^4.0.1": version: 4.0.1 resolution: "@ethereumjs/rlp@npm:4.0.1" @@ -3198,6 +3348,18 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/tx@npm:^4.1.2, @ethereumjs/tx@npm:^4.2.0": + version: 4.2.0 + resolution: "@ethereumjs/tx@npm:4.2.0" + dependencies: + "@ethereumjs/common": ^3.2.0 + "@ethereumjs/rlp": ^4.0.1 + "@ethereumjs/util": ^8.1.0 + ethereum-cryptography: ^2.0.0 + checksum: 87a3f5f2452cfbf6712f8847525a80c213210ed453c211c793c5df801fe35ecef28bae17fadd222fcbdd94277478a47e52d2b916a90a6b30cda21f1e0cdaee42 + languageName: node + linkType: hard + "@ethereumjs/util@npm:^8.1.0": version: 8.1.0 resolution: "@ethereumjs/util@npm:8.1.0" @@ -5879,13 +6041,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.19 - resolution: "@jridgewell/trace-mapping@npm:0.3.19" +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: "@jridgewell/resolve-uri": ^3.1.0 "@jridgewell/sourcemap-codec": ^1.4.14 - checksum: 956a6f0f6fec060fb48c6bf1f5ec2064e13cd38c8be3873877d4b92b4a27ba58289a34071752671262a3e3c202abcc3fa2aac64d8447b4b0fa1ba3c9047f1c20 + checksum: 9d3c40d225e139987b50c48988f8717a54a8c994d8a948ee42e1412e08988761d0754d7d10b803061cc3aebf35f92a5dbbab493bd0e1a9ef9e89a2130e83ba34 languageName: node linkType: hard @@ -5992,6 +6154,17 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-json-rpc-provider@npm:^1.0.0": + version: 1.0.1 + resolution: "@metamask/eth-json-rpc-provider@npm:1.0.1" + dependencies: + "@metamask/json-rpc-engine": ^7.0.0 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^5.0.1 + checksum: ff97648b002d2889bd020c03abc26137cf068df3280e46144b5333c1b294f35f5099361343825f900ef20b9dcb6819495830b7a99eb1cbfbd671e5b11c0dfde1 + languageName: node + linkType: hard + "@metamask/eth-sig-util@npm:^4.0.0": version: 4.0.1 resolution: "@metamask/eth-sig-util@npm:4.0.1" @@ -6015,6 +6188,77 @@ __metadata: languageName: node linkType: hard +"@metamask/json-rpc-engine@npm:^7.0.0": + version: 7.3.3 + resolution: "@metamask/json-rpc-engine@npm:7.3.3" + dependencies: + "@metamask/rpc-errors": ^6.2.1 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^8.3.0 + checksum: 7bab8b4d2341a6243ba451bc58283f0a6905b09f7257857859848a51a795444ca6899b1a6908b15f8ed236fb574ab85a630c9cb28d127ab52c4630e496c16006 + languageName: node + linkType: hard + +"@metamask/object-multiplex@npm:^1.1.0": + version: 1.3.0 + resolution: "@metamask/object-multiplex@npm:1.3.0" + dependencies: + end-of-stream: ^1.4.4 + once: ^1.4.0 + readable-stream: ^2.3.3 + checksum: 4a2b48fc0e1a8f536edbab9f37b637cd91102538ad76ce07bdfad99b90d98b34585a0e5afa62ca9c1d550a0016347568ff0d635e5bf8cfa266d049e1c0ebedc8 + languageName: node + linkType: hard + +"@metamask/onboarding@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/onboarding@npm:1.0.1" + dependencies: + bowser: ^2.9.0 + checksum: c5a6b13760d8c761733fd5edcd3984b2951fb22b34ecebc27104224de7d2582065b8b7edc5b1dafafb76e73a55144d251bc08d540620dde7f1ebfb5f3520b050 + languageName: node + linkType: hard + +"@metamask/post-message-stream@npm:^6.1.0": + version: 6.2.0 + resolution: "@metamask/post-message-stream@npm:6.2.0" + dependencies: + "@metamask/utils": ^5.0.0 + readable-stream: 2.3.3 + checksum: 657cdb2dd61a46a4da7f036a97ef0aa9ad8e918d8f8c0fd620eaede4a32c2ff909738a7dfb2b1e6099e7771fd03c3466b60fedab56e39a5cc5507927758e3cb7 + languageName: node + linkType: hard + +"@metamask/providers@npm:^10.2.1": + version: 10.2.1 + resolution: "@metamask/providers@npm:10.2.1" + dependencies: + "@metamask/object-multiplex": ^1.1.0 + "@metamask/safe-event-emitter": ^2.0.0 + "@types/chrome": ^0.0.136 + detect-browser: ^5.2.0 + eth-rpc-errors: ^4.0.2 + extension-port-stream: ^2.0.1 + fast-deep-equal: ^2.0.1 + is-stream: ^2.0.0 + json-rpc-engine: ^6.1.0 + json-rpc-middleware-stream: ^4.2.1 + pump: ^3.0.0 + webextension-polyfill-ts: ^0.25.0 + checksum: e88b2db8c4673cc6a7e47d9f0531df3fac73f05f8e9ff6d02c3420dfb3c7a82335d9c44876f2d472c44eac36d66491d2022be4f39600bee561d5de8ad59c5b07 + languageName: node + linkType: hard + +"@metamask/rpc-errors@npm:^6.2.1": + version: 6.2.1 + resolution: "@metamask/rpc-errors@npm:6.2.1" + dependencies: + "@metamask/utils": ^8.3.0 + fast-safe-stringify: ^2.0.6 + checksum: a9223c3cb9ab05734ea0dda990597f90a7cdb143efa0c026b1a970f2094fe5fa3c341ed39b1e7623be13a96b98fb2c697ef51a2e2b87d8f048114841d35ee0a9 + languageName: node + linkType: hard + "@metamask/safe-event-emitter@npm:2.0.0, @metamask/safe-event-emitter@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/safe-event-emitter@npm:2.0.0" @@ -6022,6 +6266,85 @@ __metadata: languageName: node linkType: hard +"@metamask/safe-event-emitter@npm:^3.0.0": + version: 3.1.1 + resolution: "@metamask/safe-event-emitter@npm:3.1.1" + checksum: e24db4d7c20764bfc5b025065f92518c805f0ffb1da4820078b8cff7dcae964c0f354cf053fcb7ac659de015d5ffdf21aae5e8d44e191ee8faa9066855f22653 + languageName: node + linkType: hard + +"@metamask/sdk-communication-layer@npm:0.14.3": + version: 0.14.3 + resolution: "@metamask/sdk-communication-layer@npm:0.14.3" + dependencies: + bufferutil: ^4.0.8 + cross-fetch: ^3.1.5 + date-fns: ^2.29.3 + eciesjs: ^0.3.16 + eventemitter2: ^6.4.5 + socket.io-client: ^4.5.1 + utf-8-validate: ^6.0.3 + uuid: ^8.3.2 + checksum: 1a4d89a8bef3c4a08df151a1f95d0eca65f18715a1de3e66ae3b7dd1f7cb58957edb1cba7f1af13ee037b50866d25a0402917c640e380dbbe32534cfa0764398 + languageName: node + linkType: hard + +"@metamask/sdk-install-modal-web@npm:0.14.1": + version: 0.14.1 + resolution: "@metamask/sdk-install-modal-web@npm:0.14.1" + dependencies: + "@emotion/react": ^11.10.6 + "@emotion/styled": ^11.10.6 + i18next: 22.5.1 + qr-code-styling: ^1.6.0-rc.1 + react: ^18.2.0 + react-dom: ^18.2.0 + react-i18next: ^13.2.2 + checksum: 9122f3d0395514a4a8c2a4da5d805587b4af5d2112c333ea2dd08fa9c2046aea2f0f91bddade05364653b06899b64a849a902d645307e393c557fd878cffd50b + languageName: node + linkType: hard + +"@metamask/sdk@npm:0.14.3": + version: 0.14.3 + resolution: "@metamask/sdk@npm:0.14.3" + dependencies: + "@metamask/onboarding": ^1.0.1 + "@metamask/post-message-stream": ^6.1.0 + "@metamask/providers": ^10.2.1 + "@metamask/sdk-communication-layer": 0.14.3 + "@metamask/sdk-install-modal-web": 0.14.1 + "@react-native-async-storage/async-storage": ^1.17.11 + "@types/dom-screen-wake-lock": ^1.0.0 + bowser: ^2.9.0 + cross-fetch: ^4.0.0 + eciesjs: ^0.3.15 + eth-rpc-errors: ^4.0.3 + eventemitter2: ^6.4.7 + extension-port-stream: ^2.0.1 + i18next: 22.5.1 + i18next-browser-languagedetector: ^7.1.0 + obj-multiplex: ^1.0.0 + pump: ^3.0.0 + qrcode-terminal-nooctal: ^0.12.1 + react-i18next: ^13.2.2 + react-native-webview: ^11.26.0 + readable-stream: ^2.3.7 + rollup-plugin-visualizer: ^5.9.2 + socket.io-client: ^4.5.1 + util: ^0.12.4 + uuid: ^8.3.2 + peerDependencies: + react: ^18.2.0 + react-native: "*" + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + checksum: da43da4b39c558ec2d6a472634e0b4b9fa3f3e46b4397368438c3a86764fac7e3dde386a0b50d9ce5ad43431879ce80178c01cef507eac28e67980006c82eeb6 + languageName: node + linkType: hard + "@metamask/utils@npm:^3.0.1": version: 3.6.0 resolution: "@metamask/utils@npm:3.6.0" @@ -6034,6 +6357,36 @@ __metadata: languageName: node linkType: hard +"@metamask/utils@npm:^5.0.0, @metamask/utils@npm:^5.0.1": + version: 5.0.2 + resolution: "@metamask/utils@npm:5.0.2" + dependencies: + "@ethereumjs/tx": ^4.1.2 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + semver: ^7.3.8 + superstruct: ^1.0.3 + checksum: eca82e42911b2840deb4f32f0f215c5ffd14d22d68afbbe92d3180e920e509e310777b15eab29def3448f3535b66596ceb4c23666ec846adacc8e1bb093ff882 + languageName: node + linkType: hard + +"@metamask/utils@npm:^8.3.0": + version: 8.4.0 + resolution: "@metamask/utils@npm:8.4.0" + dependencies: + "@ethereumjs/tx": ^4.2.0 + "@noble/hashes": ^1.3.1 + "@scure/base": ^1.1.3 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + pony-cause: ^2.1.10 + semver: ^7.5.4 + superstruct: ^1.0.3 + uuid: ^9.0.1 + checksum: b0397e97bac7192f6189a8625a2dfcb56d3c2cf4dd2cb3d4e012a7e9786f04f59f6917805544bc131a6dacd2c8344e237ae43ad47429bb5eb35c6cf1248440b4 + languageName: node + linkType: hard + "@motionone/animation@npm:^10.12.0, @motionone/animation@npm:^10.15.1, @motionone/animation@npm:^10.16.3": version: 10.16.3 resolution: "@motionone/animation@npm:10.16.3" @@ -8049,6 +8402,16 @@ __metadata: languageName: node linkType: hard +"@safe-global/safe-apps-provider@npm:0.18.1": + version: 0.18.1 + resolution: "@safe-global/safe-apps-provider@npm:0.18.1" + dependencies: + "@safe-global/safe-apps-sdk": ^8.1.0 + events: ^3.3.0 + checksum: fb77aee24149303a8886f1c23ed35ccd75ed63ed67cdb1dfd5c7160e7744f37c8872feadcfbf6d5712d2de65896a1aaf339dc4afb1fa648f0dddd689ff89183c + languageName: node + linkType: hard + "@safe-global/safe-apps-provider@npm:^0.17.1": version: 0.17.1 resolution: "@safe-global/safe-apps-provider@npm:0.17.1" @@ -8069,7 +8432,7 @@ __metadata: languageName: node linkType: hard -"@safe-global/safe-apps-sdk@npm:^8.0.0": +"@safe-global/safe-apps-sdk@npm:8.1.0, @safe-global/safe-apps-sdk@npm:^8.0.0, @safe-global/safe-apps-sdk@npm:^8.1.0": version: 8.1.0 resolution: "@safe-global/safe-apps-sdk@npm:8.1.0" dependencies: @@ -8086,10 +8449,10 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:~1.1.0, @scure/base@npm:~1.1.2": - version: 1.1.3 - resolution: "@scure/base@npm:1.1.3" - checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c +"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0, @scure/base@npm:~1.1.2": + version: 1.1.6 + resolution: "@scure/base@npm:1.1.6" + checksum: d6deaae91deba99e87939af9e55d80edba302674983f32bba57f942e22b1726a83c62dc50d8f4370a5d5d35a212dda167fb169f4b0d0c297488d8604608fc3d3 languageName: node linkType: hard @@ -8837,6 +9200,13 @@ __metadata: languageName: node linkType: hard +"@socket.io/component-emitter@npm:~3.1.0": + version: 3.1.0 + resolution: "@socket.io/component-emitter@npm:3.1.0" + checksum: db069d95425b419de1514dffe945cc439795f6a8ef5b9465715acf5b8b50798e2c91b8719cbf5434b3fe7de179d6cdcd503c277b7871cb3dd03febb69bdd50fa + languageName: node + linkType: hard + "@solana/buffer-layout@npm:^4.0.0": version: 4.0.1 resolution: "@solana/buffer-layout@npm:4.0.1" @@ -11439,6 +11809,24 @@ __metadata: languageName: node linkType: hard +"@tanstack/query-core@npm:5.28.13": + version: 5.28.13 + resolution: "@tanstack/query-core@npm:5.28.13" + checksum: 38608889edb2ef5c3699573185b99e9e858829d2076c5f1fc6362b51ac1bdde152dfed5b1031172395d13563f17c2f4cabc57f34f1def030ca7faa7a4a1b73f5 + languageName: node + linkType: hard + +"@tanstack/react-query@npm:5.28.14": + version: 5.28.14 + resolution: "@tanstack/react-query@npm:5.28.14" + dependencies: + "@tanstack/query-core": 5.28.13 + peerDependencies: + react: ^18.0.0 + checksum: 71ebd00796184e5366ed0d0110c722ea48909591941e3ba3018c82958d5eae5b50f97e0e8348c0cc4e1906ebb978ccad47dddc9811801cea2a07b60057027fe2 + languageName: node + linkType: hard + "@tanstack/react-table@npm:8.10.7": version: 8.10.7 resolution: "@tanstack/react-table@npm:8.10.7" @@ -11780,6 +12168,16 @@ __metadata: languageName: node linkType: hard +"@types/chrome@npm:^0.0.136": + version: 0.0.136 + resolution: "@types/chrome@npm:0.0.136" + dependencies: + "@types/filesystem": "*" + "@types/har-format": "*" + checksum: af96fdc79fb019d827fdb6269f831921f8f36215ee05a2624436dd2ad4d84d7be12333cc6f54912fb8bae0ca49cbfde5a78de94723bfbd20d309d2e71e274a1b + languageName: node + linkType: hard + "@types/connect-history-api-fallback@npm:^1.3.5": version: 1.3.5 resolution: "@types/connect-history-api-fallback@npm:1.3.5" @@ -12135,6 +12533,13 @@ __metadata: languageName: node linkType: hard +"@types/dom-screen-wake-lock@npm:^1.0.0": + version: 1.0.3 + resolution: "@types/dom-screen-wake-lock@npm:1.0.3" + checksum: 66bece3508b4f4147db97a530c758f8f5d3132ef00c06cab1db4bf2b4af6a3a614ae0a0ba6b53ddc4177a6545adf9d312547087256efc8eff7314b13221380b8 + languageName: node + linkType: hard + "@types/escodegen@npm:^0.0.6": version: 0.0.6 resolution: "@types/escodegen@npm:0.0.6" @@ -12162,7 +12567,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0": +"@types/estree@npm:*, @types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: dd8b5bed28e6213b7acd0fb665a84e693554d850b0df423ac8076cc3ad5823a6bc26b0251d080bdc545af83179ede51dd3f6fa78cad2c46ed1f29624ddf3e41a @@ -12820,12 +13225,12 @@ __metadata: languageName: node linkType: hard -"@types/secp256k1@npm:^4.0.1": - version: 4.0.3 - resolution: "@types/secp256k1@npm:4.0.3" +"@types/secp256k1@npm:^4.0.1, @types/secp256k1@npm:^4.0.4": + version: 4.0.6 + resolution: "@types/secp256k1@npm:4.0.6" dependencies: "@types/node": "*" - checksum: 1bd10b9afa724084b655dc81b7b315def3d2d0e272014ef16009fa76e17537411c07c0695fdea412bc7b36d2a02687f5fea33522d55b8ef29eda42992f812913 + checksum: 984494caf49a4ce99fda2b9ea1840eb47af946b8c2737314108949bcc0c06b4880e871296bd49ed6ea4c8423e3a302ad79fec43abfc987330e7eb98f0c4e8ba4 languageName: node linkType: hard @@ -13567,6 +13972,7 @@ __metadata: "@swc/plugin-styled-components": 1.5.97 "@tamagui/core": 1.94.3 "@tamagui/react-native-svg": 1.94.3 + "@tanstack/react-query": 5.28.14 "@tanstack/react-table": 8.10.7 "@testing-library/jest-dom": 5.17.0 "@testing-library/react": 13.4.0 @@ -13739,9 +14145,11 @@ __metadata: utilities: "workspace:^" uuid: 9.0.0 video-extensions: 1.2.0 + viem: 2.x + wagmi: 2.5.19 wcag-contrast: 3.0.0 web-vitals: 2.1.4 - webpack: 5.89.0 + webpack: 5.90.0 webpack-retry-chunk-load-plugin: 3.1.1 wrangler: 3.15.0 xml2js: 0.6.2 @@ -13840,7 +14248,6 @@ __metadata: "@walletconnect/web3wallet": 1.10.2 "@welldone-software/why-did-you-render": 7.0.1 apollo3-cache-persist: 0.14.1 - babel-jest: 29.6.1 babel-loader: 8.2.3 babel-plugin-react-native-web: 0.17.5 babel-plugin-react-require: 4.0.0 @@ -14031,6 +14438,8 @@ __metadata: "@sentry/webpack-plugin": 2.10.3 "@svgr/webpack": 8.0.1 "@tamagui/core": 1.94.3 + "@testing-library/dom": ^7.11.0 + "@testing-library/react": 13.4.0 "@types/chrome": 0.0.254 "@types/jest": 29.5.0 "@types/react": ^18.0.15 @@ -14087,7 +14496,7 @@ __metadata: utilities: "workspace:^" uuid: 9.0.0 wallet: "workspace:^" - webpack: 5.89.0 + webpack: 5.90.0 webpack-cli: ^5.0.1 webpack-dev-server: ^4.13.1 zod: 3.22.4 @@ -14534,6 +14943,47 @@ __metadata: languageName: node linkType: hard +"@wagmi/connectors@npm:4.1.25": + version: 4.1.25 + resolution: "@wagmi/connectors@npm:4.1.25" + dependencies: + "@coinbase/wallet-sdk": 3.9.1 + "@metamask/sdk": 0.14.3 + "@safe-global/safe-apps-provider": 0.18.1 + "@safe-global/safe-apps-sdk": 8.1.0 + "@walletconnect/ethereum-provider": 2.11.2 + "@walletconnect/modal": 2.6.2 + peerDependencies: + "@wagmi/core": 2.6.16 + typescript: ">=5.0.4" + viem: 2.x + peerDependenciesMeta: + typescript: + optional: true + checksum: 7b402604ca05bbe04d905f8c9c9c9441c37b0baae5d1610b999d0e86a0c0c573b937c509d901bead3b3fdbd4caaf4a47dfc0ad9115e71ebf91ce6c2d900297c9 + languageName: node + linkType: hard + +"@wagmi/core@npm:2.6.16": + version: 2.6.16 + resolution: "@wagmi/core@npm:2.6.16" + dependencies: + eventemitter3: 5.0.1 + mipd: 0.0.5 + zustand: 4.4.1 + peerDependencies: + "@tanstack/query-core": ">=5.0.0" + typescript: ">=5.0.4" + viem: 2.x + peerDependenciesMeta: + "@tanstack/query-core": + optional: true + typescript: + optional: true + checksum: 6ae6d2f98bb87e2ea039b1e403c07804021fea936882c4a03b3dd67edc8b9ecd3f5e968a321f105c3e2be7186d9980c2e6fa5ac3f4248de06e0677e7ab601fee + languageName: node + linkType: hard + "@walletconnect/auth-client@npm:2.1.2": version: 2.1.2 resolution: "@walletconnect/auth-client@npm:2.1.2" @@ -14772,7 +15222,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal@npm:^2.6.2": +"@walletconnect/modal@npm:2.6.2, @walletconnect/modal@npm:^2.6.2": version: 2.6.2 resolution: "@walletconnect/modal@npm:2.6.2" dependencies: @@ -15520,6 +15970,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.0": + version: 1.0.0 + resolution: "abitype@npm:1.0.0" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: ea2c0548c3ba58c37a6de7483d63389074da498e63d803b742bbe94eb4eaa1f51a35d000c424058b2583aef56698cf07c696eb3bc4dd0303bc20c6f0826a241a + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -17446,6 +17911,13 @@ __metadata: languageName: node linkType: hard +"bowser@npm:^2.9.0": + version: 2.11.0 + resolution: "bowser@npm:2.11.0" + checksum: 29c3f01f22e703fa6644fc3b684307442df4240b6e10f6cfe1b61c6ca5721073189ca97cdeedb376081148c8518e33b1d818a57f781d70b0b70e1f31fb48814f + languageName: node + linkType: hard + "boxen@npm:7.0.0": version: 7.0.0 resolution: "boxen@npm:7.0.0" @@ -17710,7 +18182,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.18.1, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.1, browserslist@npm:^4.22.2": +"browserslist@npm:^4.0.0, browserslist@npm:^4.18.1, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.1, browserslist@npm:^4.22.2": version: 4.22.2 resolution: "browserslist@npm:4.22.2" dependencies: @@ -17864,7 +18336,7 @@ __metadata: languageName: node linkType: hard -"bufferutil@npm:^4.0.1": +"bufferutil@npm:^4.0.1, bufferutil@npm:^4.0.8": version: 4.0.8 resolution: "bufferutil@npm:4.0.8" dependencies: @@ -18974,7 +19446,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:1.2.1, clsx@npm:^1.1.0": +"clsx@npm:1.2.1, clsx@npm:^1.1.0, clsx@npm:^1.2.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 @@ -19898,6 +20370,15 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:^4.0.0": + version: 4.0.0 + resolution: "cross-fetch@npm:4.0.0" + dependencies: + node-fetch: ^2.6.12 + checksum: ecca4f37ffa0e8283e7a8a590926b66713a7ef7892757aa36c2d20ffa27b0ac5c60dcf453119c809abe5923fc0bae3702a4d896bfb406ef1077b0d0018213e24 + languageName: node + linkType: hard + "cross-inspect@npm:1.0.0": version: 1.0.0 resolution: "cross-inspect@npm:1.0.0" @@ -21040,7 +21521,7 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:2.30.0, date-fns@npm:^2.30.0": +"date-fns@npm:2.30.0, date-fns@npm:^2.29.3, date-fns@npm:^2.30.0": version: 2.30.0 resolution: "date-fns@npm:2.30.0" dependencies: @@ -21093,7 +21574,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -21576,7 +22057,7 @@ __metadata: languageName: node linkType: hard -"detect-browser@npm:5.3.0": +"detect-browser@npm:5.3.0, detect-browser@npm:^5.2.0": version: 5.3.0 resolution: "detect-browser@npm:5.3.0" checksum: dd6e08d55da1d9e0f22510ac79872078ae03d9dfa13c5e66c96baedc1c86567345a88f96949161f6be8f3e0fafa93bf179bdb1cd311b14f5f163112fcc70ab49 @@ -22397,6 +22878,17 @@ __metadata: languageName: node linkType: hard +"eciesjs@npm:^0.3.15, eciesjs@npm:^0.3.16": + version: 0.3.18 + resolution: "eciesjs@npm:0.3.18" + dependencies: + "@types/secp256k1": ^4.0.4 + futoin-hkdf: ^1.5.3 + secp256k1: ^5.0.0 + checksum: 2d6e1624c4b2110ab4c76a684d0f458774c702f9711859404a52ede1b2dea67f61e8fc258a0867c2090e5b1110ca3201ea2876f5ac0e2dd57ef1bcfb358d3004 + languageName: node + linkType: hard + "edit-json-file@npm:^1.7.0": version: 1.8.0 resolution: "edit-json-file@npm:1.8.0" @@ -22448,7 +22940,7 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:6.5.4, elliptic@npm:^6.5.2, elliptic@npm:^6.5.3": +"elliptic@npm:6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" dependencies: @@ -22463,6 +22955,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.2, elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": + version: 6.5.5 + resolution: "elliptic@npm:6.5.5" + dependencies: + bn.js: ^4.11.9 + brorand: ^1.1.0 + hash.js: ^1.0.0 + hmac-drbg: ^1.0.1 + inherits: ^2.0.4 + minimalistic-assert: ^1.0.1 + minimalistic-crypto-utils: ^1.0.1 + checksum: ec9105e4469eb3b32b0ee2579756c888ddf3f99d259aa0d65fccb906ee877768aaf8880caae73e3e669c9a4adeb3eb1945703aa974ec5000d2d33a239f4567eb + languageName: node + linkType: hard + "emittery@npm:^0.10.2": version: 0.10.2 resolution: "emittery@npm:0.10.2" @@ -22535,7 +23042,7 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": +"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.0, end-of-stream@npm:^1.4.1, end-of-stream@npm:^1.4.4": version: 1.4.4 resolution: "end-of-stream@npm:1.4.4" dependencies: @@ -22544,6 +23051,26 @@ __metadata: languageName: node linkType: hard +"engine.io-client@npm:~6.5.2": + version: 6.5.3 + resolution: "engine.io-client@npm:6.5.3" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.1 + engine.io-parser: ~5.2.1 + ws: ~8.11.0 + xmlhttprequest-ssl: ~2.0.0 + checksum: a72596fae99afbdb899926fccdb843f8fa790c69085b881dde121285a6935da2c2c665ebe88e0e6aa4285637782df84ac882084ff4892ad2430b059fc0045db0 + languageName: node + linkType: hard + +"engine.io-parser@npm:~5.2.1": + version: 5.2.2 + resolution: "engine.io-parser@npm:5.2.2" + checksum: 470231215f3136a9259efb1268bc9a71f789af4e8c74da8d3b49ceb149fe3cd5c315bf0cd13d2d8d9c8f0f051c6f93b68e8fa9c89a3b612b9217bf33765c943a + languageName: node + linkType: hard + "enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.8.3": version: 5.15.0 resolution: "enhanced-resolve@npm:5.15.0" @@ -23676,6 +24203,19 @@ __metadata: languageName: node linkType: hard +"eth-block-tracker@npm:^7.1.0": + version: 7.1.0 + resolution: "eth-block-tracker@npm:7.1.0" + dependencies: + "@metamask/eth-json-rpc-provider": ^1.0.0 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^5.0.1 + json-rpc-random-id: ^1.0.1 + pify: ^3.0.0 + checksum: 1d019f261e0ef07387cd74538b160700caa35ba9859ab9d4e5137c48bf9c92822c3b4ade40f8a504f16cb813de4c317c5378d047625ddf04592e256be8842588 + languageName: node + linkType: hard + "eth-json-rpc-filters@npm:5.1.0": version: 5.1.0 resolution: "eth-json-rpc-filters@npm:5.1.0" @@ -23689,6 +24229,19 @@ __metadata: languageName: node linkType: hard +"eth-json-rpc-filters@npm:^6.0.0": + version: 6.0.1 + resolution: "eth-json-rpc-filters@npm:6.0.1" + dependencies: + "@metamask/safe-event-emitter": ^3.0.0 + async-mutex: ^0.2.6 + eth-query: ^2.1.2 + json-rpc-engine: ^6.1.0 + pify: ^5.0.0 + checksum: 216f7417417599a48273b08fb2894581175276fe21cb1c9ffa66e98a9c2a67bc0ac821ad2ca163fdb8e8de0960aea0d9c5e53aee9d5dcfec355abf020e9458c5 + languageName: node + linkType: hard + "eth-query@npm:^2.1.2": version: 2.1.2 resolution: "eth-query@npm:2.1.2" @@ -23708,7 +24261,7 @@ __metadata: languageName: node linkType: hard -"eth-rpc-errors@npm:4.0.3, eth-rpc-errors@npm:^4.0.2": +"eth-rpc-errors@npm:4.0.3, eth-rpc-errors@npm:^4.0.2, eth-rpc-errors@npm:^4.0.3": version: 4.0.3 resolution: "eth-rpc-errors@npm:4.0.3" dependencies: @@ -23902,6 +24455,13 @@ __metadata: languageName: node linkType: hard +"eventemitter2@npm:^6.4.5, eventemitter2@npm:^6.4.7": + version: 6.4.9 + resolution: "eventemitter2@npm:6.4.9" + checksum: be59577c1e1c35509c7ba0e2624335c35bbcfd9485b8a977384c6cc6759341ea1a98d3cb9dbaa5cea4fff9b687e504504e3f9c2cc1674cf3bd8a43a7c74ea3eb + languageName: node + linkType: hard + "eventemitter3@npm:5.0.1, eventemitter3@npm:^5.0.1": version: 5.0.1 resolution: "eventemitter3@npm:5.0.1" @@ -24526,6 +25086,15 @@ __metadata: languageName: node linkType: hard +"extension-port-stream@npm:^2.0.1": + version: 2.1.1 + resolution: "extension-port-stream@npm:2.1.1" + dependencies: + webextension-polyfill: ">=0.10.0 <1.0" + checksum: aee8bbeb2ed6f69a62f58a89580e0e9002dadb11062edbaedb7bb04cfc5a5e0b0d3980bfeaa1c3ee7e08dec7e5fac26e25497fc2f82000db7653442bd5eca157 + languageName: node + linkType: hard + "external-editor@npm:^3.0.3": version: 3.1.0 resolution: "external-editor@npm:3.1.0" @@ -24626,6 +25195,13 @@ __metadata: languageName: node linkType: hard +"fast-deep-equal@npm:^2.0.1": + version: 2.0.1 + resolution: "fast-deep-equal@npm:2.0.1" + checksum: b701835a87985e0ec4925bdf1f0c1e7eb56309b5d12d534d5b4b69d95a54d65bb16861c081781ead55f73f12d6c60ba668713391ee7fbf6b0567026f579b7b0b + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -25693,6 +26269,13 @@ __metadata: languageName: node linkType: hard +"futoin-hkdf@npm:^1.5.3": + version: 1.5.3 + resolution: "futoin-hkdf@npm:1.5.3" + checksum: 790da5675b31df4b9a34c19a5181f673171b5ad81fa92b91981bcfd2250692f895d6aada5ae4203212babba3c7d7a1916432e0b42c7aa88d3f70408439ec315e + languageName: node + linkType: hard + "gauge@npm:^3.0.0": version: 3.0.2 resolution: "gauge@npm:3.0.2" @@ -26890,7 +27473,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -27328,6 +27911,15 @@ __metadata: languageName: node linkType: hard +"i18next-browser-languagedetector@npm:^7.1.0": + version: 7.2.1 + resolution: "i18next-browser-languagedetector@npm:7.2.1" + dependencies: + "@babel/runtime": ^7.23.2 + checksum: 159958be2d8f19444e9378512c36c2bf13a8ab85eddac2fc0000198a03dbc28c73a6f44594ab040b242bdc82dfeabb7c1ab805884b5438ee0a48a8e2b52ca062 + languageName: node + linkType: hard + "i18next-parser@npm:8.6.0": version: 8.6.0 resolution: "i18next-parser@npm:8.6.0" @@ -27376,6 +27968,15 @@ __metadata: languageName: node linkType: hard +"i18next@npm:22.5.1, i18next@npm:^22.0.4": + version: 22.5.1 + resolution: "i18next@npm:22.5.1" + dependencies: + "@babel/runtime": ^7.20.6 + checksum: 175f8ab7fac2abcee147b00cc2d8e7d4fa9b05cdc227f02cac841fc2fd9545ed4a6d88774f594f8ad12dc944e4d34cc8e88aa00c8b9947baef9e859d93abd305 + languageName: node + linkType: hard + "i18next@npm:23.10.0": version: 23.10.0 resolution: "i18next@npm:23.10.0" @@ -27385,15 +27986,6 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^22.0.4": - version: 22.5.1 - resolution: "i18next@npm:22.5.1" - dependencies: - "@babel/runtime": ^7.20.6 - checksum: 175f8ab7fac2abcee147b00cc2d8e7d4fa9b05cdc227f02cac841fc2fd9545ed4a6d88774f594f8ad12dc944e4d34cc8e88aa00c8b9947baef9e859d93abd305 - languageName: node - linkType: hard - "iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -29666,6 +30258,7 @@ __metadata: resolution: "jest-presets@workspace:config/jest-presets" dependencies: "@testing-library/jest-native": 5.4.2 + babel-jest: 29.6.1 jest-svg-transformer: 1.0.0 jest-transform-stub: 2.0.0 mem-storage-area: 1.0.3 @@ -30610,6 +31203,17 @@ __metadata: languageName: node linkType: hard +"json-rpc-middleware-stream@npm:^4.2.1": + version: 4.2.3 + resolution: "json-rpc-middleware-stream@npm:4.2.3" + dependencies: + "@metamask/safe-event-emitter": ^3.0.0 + json-rpc-engine: ^6.1.0 + readable-stream: ^2.3.3 + checksum: 0907d34935a8b58c3c67626e344272758f684c13175b2e7de2bac37309c3211fca7a129bce042d50faed605615f51fbba01e173bdc2ae6c14d95aefb9bfb4e09 + languageName: node + linkType: hard + "json-rpc-random-id@npm:^1.0.0, json-rpc-random-id@npm:^1.0.1": version: 1.0.1 resolution: "json-rpc-random-id@npm:1.0.1" @@ -30848,7 +31452,7 @@ __metadata: languageName: node linkType: hard -"keccak@npm:^3.0.0, keccak@npm:^3.0.1, keccak@npm:^3.0.2": +"keccak@npm:^3.0.0, keccak@npm:^3.0.1, keccak@npm:^3.0.2, keccak@npm:^3.0.3": version: 3.0.4 resolution: "keccak@npm:3.0.4" dependencies: @@ -33214,6 +33818,20 @@ __metadata: languageName: node linkType: hard +"mipd@npm:0.0.5": + version: 0.0.5 + resolution: "mipd@npm:0.0.5" + dependencies: + viem: ^1.1.4 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 920b3afb3a92daeb66685adb746211ac4268ff805392a2f6e1da2703c43aa5835e5ad7be9d0312582a348d4c764ae3c81ab39362b66607ba0d54032def0038f1 + languageName: node + linkType: hard + "mixin-deep@npm:^1.2.0": version: 1.3.2 resolution: "mixin-deep@npm:1.3.2" @@ -33761,6 +34379,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: latest + checksum: 2508bd2d2981945406243a7bd31362fc7af8b70b8b4d65f869c61731800058fb818cc2fd36c8eac714ddd0e568cc85becf5e165cebbdf7b5024d5151bbc75ea1 + languageName: node + linkType: hard + "node-addon-api@npm:^7.0.0": version: 7.0.0 resolution: "node-addon-api@npm:7.0.0" @@ -34488,6 +35115,17 @@ __metadata: languageName: node linkType: hard +"obj-multiplex@npm:^1.0.0": + version: 1.0.0 + resolution: "obj-multiplex@npm:1.0.0" + dependencies: + end-of-stream: ^1.4.0 + once: ^1.4.0 + readable-stream: ^2.3.3 + checksum: 6bdcb7d48a1cd4458a7ff0be0b3c1dc58e8e9e6504f937c10b1eac096a3d459b85d7ba32bdd9a45382bb238e245eb42ebcd91430c72f04b0a57c97f846f2d06f + languageName: node + linkType: hard + "object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -35913,6 +36551,13 @@ __metadata: languageName: node linkType: hard +"pony-cause@npm:^2.1.10": + version: 2.1.10 + resolution: "pony-cause@npm:2.1.10" + checksum: 8b61378f213e61056312dc274a1c79980154e9d864f6ad86e0c8b91a50d3ce900d430995ee24147c9f3caa440dfe7d51c274b488d7f033b65b206522536d7217 + languageName: node + linkType: hard + "popmotion@npm:11.0.3": version: 11.0.3 resolution: "popmotion@npm:11.0.3" @@ -36804,10 +37449,10 @@ __metadata: languageName: node linkType: hard -"preact@npm:^10.5.9": - version: 10.19.1 - resolution: "preact@npm:10.19.1" - checksum: e27f786ca3c7426fb475dd312dce4b6a99b1d6dd448543d8508a25c2eca89149d345e7c8685001527872a6299c8605c06e789a55952f297a9722ec55c2d2b669 +"preact@npm:^10.16.0, preact@npm:^10.5.9": + version: 10.20.1 + resolution: "preact@npm:10.20.1" + checksum: af5ed9bdf44bfa5487479c09fa971a32902987f277c74d6244f9d9466ccd5c1efd3a0949e7dc2227177623878b1adafcc5c50cee6617a84f72d1cebe55ff76ba languageName: node linkType: hard @@ -37049,6 +37694,13 @@ __metadata: languageName: node linkType: hard +"process-nextick-args@npm:~1.0.6": + version: 1.0.7 + resolution: "process-nextick-args@npm:1.0.7" + checksum: 41224fbc803ac6c96907461d4dfc20942efa3ca75f2d521bcf7cf0e89f8dec127fb3fb5d76746b8fb468a232ea02d84824fae08e027aec185fd29049c66d49f8 + languageName: node + linkType: hard + "process-warning@npm:^1.0.0": version: 1.0.0 resolution: "process-warning@npm:1.0.0" @@ -37347,6 +37999,15 @@ __metadata: languageName: node linkType: hard +"qr-code-styling@npm:^1.6.0-rc.1": + version: 1.6.0-rc.1 + resolution: "qr-code-styling@npm:1.6.0-rc.1" + dependencies: + qrcode-generator: ^1.4.3 + checksum: 778754790fe0b586ecd38fb02de777c7dd9cf844cf6e3c88f9a23ad85b122200a8567c946e3c41dba84ddd2f0016aa31ddfd1507150e1dbfea8a58323b62d944 + languageName: node + linkType: hard + "qr.js@npm:0.0.0": version: 0.0.0 resolution: "qr.js@npm:0.0.0" @@ -37354,6 +38015,22 @@ __metadata: languageName: node linkType: hard +"qrcode-generator@npm:^1.4.3": + version: 1.4.4 + resolution: "qrcode-generator@npm:1.4.4" + checksum: 860cfdd2a7a608d34e92cab99774cc08182e1911432f30ed36d16f8a5cdabd7fdf40239caed91fa2691cfe66c8d95c1340a2fc9cc439eed07a9f2eb328c6f527 + languageName: node + linkType: hard + +"qrcode-terminal-nooctal@npm:^0.12.1": + version: 0.12.1 + resolution: "qrcode-terminal-nooctal@npm:0.12.1" + bin: + qrcode-terminal: bin/qrcode-terminal.js + checksum: 1071c4be2bfa07b3956ad0a63c87452ced0b5180a9dc19f224fc3dd69bb24ad687a7af365acdde0f876ddf89dc1a4beadba88d89c7c5c5cbf2ef3efaef64736e + languageName: node + linkType: hard + "qrcode-terminal@npm:0.11.0": version: 0.11.0 resolution: "qrcode-terminal@npm:0.11.0" @@ -37851,6 +38528,24 @@ __metadata: languageName: node linkType: hard +"react-i18next@npm:^13.2.2": + version: 13.5.0 + resolution: "react-i18next@npm:13.5.0" + dependencies: + "@babel/runtime": ^7.22.5 + html-parse-stringify: ^3.0.1 + peerDependencies: + i18next: ">= 23.2.3" + react: ">= 16.8.0" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 2f68ccd24daf72ddd2d11a526fb3c2b66c11ea4fcd2e24ac7aed42bf57ec7bffa7455ad1dc93679968ff629e9b1896465cdf6d1a61c29b92138ef88098e8dcba + languageName: node + linkType: hard + "react-infinite-scroll-component@npm:6.1.0": version: 6.1.0 resolution: "react-infinite-scroll-component@npm:6.1.0" @@ -38464,6 +39159,19 @@ __metadata: languageName: node linkType: hard +"react-native-webview@npm:^11.26.0": + version: 11.26.1 + resolution: "react-native-webview@npm:11.26.1" + dependencies: + escape-string-regexp: 2.0.0 + invariant: 2.2.4 + peerDependencies: + react: "*" + react-native: "*" + checksum: d2f95a89e944a2f1e8cf402e4e274f3568edae42e7ef190915e9fba8004a01d699c962459bdc9688c159060538e90aea3017cab24e6f4112021cbbc10ef57104 + languageName: node + linkType: hard + "react-native-widgetkit@npm:1.0.9": version: 1.0.9 resolution: "react-native-widgetkit@npm:1.0.9" @@ -39046,6 +39754,21 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:2.3.3": + version: 2.3.3 + resolution: "readable-stream@npm:2.3.3" + dependencies: + core-util-is: ~1.0.0 + inherits: ~2.0.3 + isarray: ~1.0.0 + process-nextick-args: ~1.0.6 + safe-buffer: ~5.1.1 + string_decoder: ~1.0.3 + util-deprecate: ~1.0.1 + checksum: 76f9863065d7edc14abd78e68784048487e83a4b6908336ba3eacb5e9544d642ad60836f91fab16e1dc6ad9e493dfe6c2e5b65f370ec65454d415efa50361a76 + languageName: node + linkType: hard + "readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" @@ -39057,7 +39780,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.0.6, readable-stream@npm:^2.1.5, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.6, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.0.6, readable-stream@npm:^2.1.5, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.6, readable-stream@npm:^2.3.7, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -40182,6 +40905,25 @@ __metadata: languageName: node linkType: hard +"rollup-plugin-visualizer@npm:^5.9.2": + version: 5.12.0 + resolution: "rollup-plugin-visualizer@npm:5.12.0" + dependencies: + open: ^8.4.0 + picomatch: ^2.3.1 + source-map: ^0.7.4 + yargs: ^17.5.1 + peerDependencies: + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rollup: + optional: true + bin: + rollup-plugin-visualizer: dist/bin/cli.js + checksum: 17dc10a93d4bd457c8bb7796a57c284487fb00f4b9703a33a1a954f5d40c66a89b24aca98564569922456f4fa8f72281c3ef96a95502195e6930b3fac62fce8e + languageName: node + linkType: hard + "rollup-pluginutils@npm:^2.8.1": version: 2.8.2 resolution: "rollup-pluginutils@npm:2.8.2" @@ -40576,6 +41318,18 @@ __metadata: languageName: node linkType: hard +"secp256k1@npm:^5.0.0": + version: 5.0.0 + resolution: "secp256k1@npm:5.0.0" + dependencies: + elliptic: ^6.5.4 + node-addon-api: ^5.0.0 + node-gyp: latest + node-gyp-build: ^4.2.0 + checksum: a0719dff4687c38d385b5e0b7e811c51a4ea24893128be9d097aee99f879eb0ea52582590deb15a49da627a3db23c6b028ad5c9c6ac1fca92ce760153b8cf21c + languageName: node + linkType: hard + "select-hose@npm:^2.0.0": version: 2.0.0 resolution: "select-hose@npm:2.0.0" @@ -41206,6 +41960,28 @@ __metadata: languageName: node linkType: hard +"socket.io-client@npm:^4.5.1": + version: 4.7.5 + resolution: "socket.io-client@npm:4.7.5" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.2 + engine.io-client: ~6.5.2 + socket.io-parser: ~4.2.4 + checksum: a6994b93a753d14292682ee97ba3c925c54b63e6fcb2ed5e0aa1d7c1d6164ed4a30d993f7eaaa3017ddf868ad0a1ab996badc8310129070136d84668789ee6c9 + languageName: node + linkType: hard + +"socket.io-parser@npm:~4.2.4": + version: 4.2.4 + resolution: "socket.io-parser@npm:4.2.4" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.1 + checksum: 61540ef99af33e6a562b9effe0fad769bcb7ec6a301aba5a64b3a8bccb611a0abdbe25f469933ab80072582006a78ca136bf0ad8adff9c77c9953581285e2263 + languageName: node + linkType: hard + "sockjs@npm:^0.3.24": version: 0.3.24 resolution: "sockjs@npm:0.3.24" @@ -42082,6 +42858,15 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~1.0.3": + version: 1.0.3 + resolution: "string_decoder@npm:1.0.3" + dependencies: + safe-buffer: ~5.1.0 + checksum: 57ef02a148fd1ff2f20fe1accd944505ed3703e78bb28d302d940b2ad3dfb469508f79dcd0275ba1960d9675aa206452f76b2416059a6d0b0200bd7e9f552cdb + languageName: node + linkType: hard + "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -42316,6 +43101,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 0eb6cc1b866dc17a6037d0a82ac7fa877eba6a757443e79e7c4f35bacedbf6421fadcab4363b39667b43355cbaaa570a3cde850f776498e5450f32ed2f9b7584 + languageName: node + linkType: hard + "stylus-lookup@npm:^3.0.1": version: 3.0.2 resolution: "stylus-lookup@npm:3.0.2" @@ -42954,7 +43746,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:5.3.9, terser-webpack-plugin@npm:^5.2.5, terser-webpack-plugin@npm:^5.3.7": +"terser-webpack-plugin@npm:5.3.9": version: 5.3.9 resolution: "terser-webpack-plugin@npm:5.3.9" dependencies: @@ -42976,7 +43768,29 @@ __metadata: languageName: node linkType: hard -"terser@npm:5.24.0, terser@npm:^5.0.0, terser@npm:^5.10.0, terser@npm:^5.15.0, terser@npm:^5.16.8": +"terser-webpack-plugin@npm:^5.2.5, terser-webpack-plugin@npm:^5.3.10": + version: 5.3.10 + resolution: "terser-webpack-plugin@npm:5.3.10" + dependencies: + "@jridgewell/trace-mapping": ^0.3.20 + jest-worker: ^27.4.5 + schema-utils: ^3.1.1 + serialize-javascript: ^6.0.1 + terser: ^5.26.0 + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: bd6e7596cf815f3353e2a53e79cbdec959a1b0276f5e5d4e63e9d7c3c5bb5306df567729da287d1c7b39d79093e56863c569c42c6c24cc34c76aa313bd2cbcea + languageName: node + linkType: hard + +"terser@npm:5.24.0": version: 5.24.0 resolution: "terser@npm:5.24.0" dependencies: @@ -42990,6 +43804,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.0.0, terser@npm:^5.10.0, terser@npm:^5.15.0, terser@npm:^5.16.8, terser@npm:^5.26.0": + version: 5.30.3 + resolution: "terser@npm:5.30.3" + dependencies: + "@jridgewell/source-map": ^0.3.3 + acorn: ^8.8.2 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: 8c680ed32a948f806fade0969c52aab94b6de174e4a78610f5d3abf9993b161eb19b88b2ceadff09b153858727c02deb6709635e4bfbd519f67d54e0394e2983 + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -44938,6 +45766,16 @@ __metadata: languageName: node linkType: hard +"utf-8-validate@npm:^6.0.3": + version: 6.0.3 + resolution: "utf-8-validate@npm:6.0.3" + dependencies: + node-gyp: latest + node-gyp-build: ^4.3.0 + checksum: 5e21383c81ff7469c1912119ca69d07202d944c73ddd8a54b84dddcc546b939054e5101c78c294e494d206fe93bd43428adc635a0660816b3ec9c8ec89286ac4 + languageName: node + linkType: hard + "utf8-byte-length@npm:^1.0.1": version: 1.0.4 resolution: "utf8-byte-length@npm:1.0.4" @@ -45083,7 +45921,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.0": +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" bin: @@ -45248,11 +46086,32 @@ __metadata: languageName: node linkType: hard -"viem@npm:^1.0.0": - version: 1.19.1 - resolution: "viem@npm:1.19.1" +"viem@npm:2.x": + version: 2.9.9 + resolution: "viem@npm:2.9.9" dependencies: - "@adraffy/ens-normalize": 1.9.4 + "@adraffy/ens-normalize": 1.10.0 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@scure/bip32": 1.3.2 + "@scure/bip39": 1.2.1 + abitype: 1.0.0 + isows: 1.0.3 + ws: 8.13.0 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 4504f6f78978834352b4fa48a433ccb587b913f7783023ee8fd42446589efdc748bf9789e43e1947020815dced9e488c5c375dda961db3f85393a756653ce2a8 + languageName: node + linkType: hard + +"viem@npm:^1.0.0, viem@npm:^1.1.4": + version: 1.21.4 + resolution: "viem@npm:1.21.4" + dependencies: + "@adraffy/ens-normalize": 1.10.0 "@noble/curves": 1.2.0 "@noble/hashes": 1.3.2 "@scure/bip32": 1.3.2 @@ -45265,7 +46124,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 5ebb8d26894f4fc1f95c2510389827da19623b2b1f150ce95cb80680ee6e28aa9cb16f3e1a00662a0b81f8de301075e2349bbd14bac2ab90c55c5694092a38b0 + checksum: c351fdea2d53d2d781ac73c964348b3b9fc5dd46f9eb53903e867705fc9e30a893cb9f2c8d7a00acdcdeca27d14eeebf976eed9f948c28c47018dc9211369117 languageName: node linkType: hard @@ -45459,6 +46318,25 @@ __metadata: languageName: node linkType: hard +"wagmi@npm:2.5.19": + version: 2.5.19 + resolution: "wagmi@npm:2.5.19" + dependencies: + "@wagmi/connectors": 4.1.25 + "@wagmi/core": 2.6.16 + use-sync-external-store: 1.2.0 + peerDependencies: + "@tanstack/react-query": ">=5.0.0" + react: ">=18" + typescript: ">=5.0.4" + viem: 2.x + peerDependenciesMeta: + typescript: + optional: true + checksum: bdf767c0de083a485d9e4e13dc98e68968aac576eb7be9c1a59e3f9ea9eb6316edfb47b19238eb68c9f151ca5df95bf859c17c2003211f4df841b0ae72c46419 + languageName: node + linkType: hard + "wait-on@npm:7.0.1": version: 7.0.1 resolution: "wait-on@npm:7.0.1" @@ -45587,7 +46465,6 @@ __metadata: redux: 4.2.1 redux-saga: 1.2.2 redux-saga-test-plan: 4.0.4 - statsig-react-native: 4.11.0 typed-redux-saga: 1.5.0 typescript: 5.3.3 ui: "workspace:^" @@ -45703,6 +46580,29 @@ __metadata: languageName: node linkType: hard +"webextension-polyfill-ts@npm:^0.25.0": + version: 0.25.0 + resolution: "webextension-polyfill-ts@npm:0.25.0" + dependencies: + webextension-polyfill: ^0.7.0 + checksum: c4dc82c86e34cea717a26af549f2822d63e92af52632f5e909ea13b5e7bd9d6110781f10313e36ada2b54c770ebca018bc3784756d12ba3b0b623d285f1a14a7 + languageName: node + linkType: hard + +"webextension-polyfill@npm:>=0.10.0 <1.0": + version: 0.10.0 + resolution: "webextension-polyfill@npm:0.10.0" + checksum: 4a59036bda571360c2c0b2fb03fe1dc244f233946bcf9a6766f677956c40fd14d270aaa69cdba95e4ac521014afbe4008bfa5959d0ac39f91c990eb206587f91 + languageName: node + linkType: hard + +"webextension-polyfill@npm:^0.7.0": + version: 0.7.0 + resolution: "webextension-polyfill@npm:0.7.0" + checksum: fb738a5de07feb593875e02f25c3ab4276c8736118929556c8d4bdf965bb0f11c96ea263cd397b9b21259e8faf2dce2eaaa42ce08c922d96de7adb5896ec7d10 + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -45900,18 +46800,18 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.89.0, webpack@npm:^5.64.4": - version: 5.89.0 - resolution: "webpack@npm:5.89.0" +"webpack@npm:5.90.0, webpack@npm:^5.64.4": + version: 5.90.0 + resolution: "webpack@npm:5.90.0" dependencies: "@types/eslint-scope": ^3.7.3 - "@types/estree": ^1.0.0 + "@types/estree": ^1.0.5 "@webassemblyjs/ast": ^1.11.5 "@webassemblyjs/wasm-edit": ^1.11.5 "@webassemblyjs/wasm-parser": ^1.11.5 acorn: ^8.7.1 acorn-import-assertions: ^1.9.0 - browserslist: ^4.14.5 + browserslist: ^4.21.10 chrome-trace-event: ^1.0.2 enhanced-resolve: ^5.15.0 es-module-lexer: ^1.2.1 @@ -45925,7 +46825,7 @@ __metadata: neo-async: ^2.6.2 schema-utils: ^3.2.0 tapable: ^2.1.1 - terser-webpack-plugin: ^5.3.7 + terser-webpack-plugin: ^5.3.10 watchpack: ^2.4.0 webpack-sources: ^3.2.3 peerDependenciesMeta: @@ -45933,7 +46833,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 43fe0dbc30e168a685ef5a86759d5016a705f6563b39a240aa00826a80637d4a3deeb8062e709d6a4b05c63e796278244c84b04174704dc4a37bedb0f565c5ed + checksum: 178a0e7e9e5b26264a19dd5fe554a3508a8afafc9cce972bfd4452b5128d0db1b37832f5e615be1cff1934f24da0de967929f199be2b3fe283ca1951f98ea3fe languageName: node linkType: hard @@ -46605,6 +47505,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:~8.11.0": + version: 8.11.0 + resolution: "ws@npm:8.11.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 316b33aba32f317cd217df66dbfc5b281a2f09ff36815de222bc859e3424d83766d9eb2bd4d667de658b6ab7be151f258318fb1da812416b30be13103e5b5c67 + languageName: node + linkType: hard + "x-is-string@npm:^0.1.0": version: 0.1.0 resolution: "x-is-string@npm:0.1.0" @@ -46717,6 +47632,13 @@ __metadata: languageName: node linkType: hard +"xmlhttprequest-ssl@npm:~2.0.0": + version: 2.0.0 + resolution: "xmlhttprequest-ssl@npm:2.0.0" + checksum: 1e98df67f004fec15754392a131343ea92e6ab5ac4d77e842378c5c4e4fd5b6a9134b169d96842cc19422d77b1606b8df84a5685562b3b698cb68441636f827e + languageName: node + linkType: hard + "xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:^4.0.2, xtend@npm:~4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" @@ -46980,6 +47902,26 @@ __metadata: languageName: node linkType: hard +"zustand@npm:4.4.1": + version: 4.4.1 + resolution: "zustand@npm:4.4.1" + dependencies: + use-sync-external-store: 1.2.0 + peerDependencies: + "@types/react": ">=16.8" + immer: ">=9.0" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + checksum: 80acd0fbf633782996642802c8692bbb80ae5c80a8dff4c501b88250acd5ccd468fbc6398bdce198475a25e3839c91385b81da921274f33ffb5c2d08c3eab400 + languageName: node + linkType: hard + "zustand@npm:4.4.6, zustand@npm:^4.3.8": version: 4.4.6 resolution: "zustand@npm:4.4.6"