ci(release): publish latest release

parent fcd04a4f
diff --git a/package.json b/package.json diff --git a/package.json b/package.json
index 251da97dcf5a092c3d2d766e16fe9536b411b9a2..7f74152f2e127a42bac946fcab641b03316e42d9 100644 index e4b37644f3a62171deaff6dbf7731979ce751c78..7b2ca64565c0c6318ba01ff26cb4fad1c7419b0d 100644
--- a/package.json --- a/package.json
+++ b/package.json +++ b/package.json
@@ -15,24 +15,10 @@ @@ -15,22 +15,9 @@
"url": "https://github.com/sponsors/tannerlinsley" "url": "https://github.com/sponsors/tannerlinsley"
}, },
"type": "module", "type": "module",
- "types": "build/legacy/index.d.ts", - "types": "build/legacy/index.d.ts",
- "main": "build/legacy/index.cjs", - "main": "build/legacy/index.cjs",
- "module": "build/legacy/index.js", - "module": "build/legacy/index.js",
+ "types": "build/modern/index.d.ts",
+ "main": "build/modern/index.cjs",
+ "module": "build/modern/index.js",
"react-native": "src/index.ts",
- "exports": { - "exports": {
- ".": { - ".": {
- "@tanstack/custom-condition": "./src/index.ts",
- "import": { - "import": {
- "types": "./build/modern/index.d.ts", - "types": "./build/modern/index.d.ts",
- "default": "./build/modern/index.js" - "default": "./build/modern/index.js"
...@@ -27,6 +22,9 @@ index 251da97dcf5a092c3d2d766e16fe9536b411b9a2..7f74152f2e127a42bac946fcab641b03 ...@@ -27,6 +22,9 @@ index 251da97dcf5a092c3d2d766e16fe9536b411b9a2..7f74152f2e127a42bac946fcab641b03
- }, - },
- "./package.json": "./package.json" - "./package.json": "./package.json"
- }, - },
+ "types": "build/modern/index.d.ts",
+ "main": "build/modern/index.cjs",
+ "module": "build/modern/index.js",
"sideEffects": false, "sideEffects": false,
"files": [ "files": [
"build", "build",
* @uniswap/web-admins
IPFS hash of the deployment: We are back with a large update: Smart wallets are here! Enable smart wallets from your home screen to benefit from faster, lower-cost transactions, enabled via an EIP 7702 smart contract.
- CIDv0: `QmdVVhTjJqcLTWyz6A2DAUNM84F6gESEzYGShEWMb7Giid`
- CIDv1: `bafybeihbenlz6ecs7jlsn6yhsykx6wounpbqw7l42nuwu7e6zd7hzfbqvi`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeihbenlz6ecs7jlsn6yhsykx6wounpbqw7l42nuwu7e6zd7hzfbqvi.ipfs.dweb.link/
- [ipfs://QmdVVhTjJqcLTWyz6A2DAUNM84F6gESEzYGShEWMb7Giid/](ipfs://QmdVVhTjJqcLTWyz6A2DAUNM84F6gESEzYGShEWMb7Giid/)
### 5.88.7 (2025-06-13)
### Bug Fixes
* **web:** fix delta formatting error (#20860) 0b9ac1f
Other changes:
- When connected to Uniswap Web, you’ll see a new verification check mark so that you know you’re on the right website.
- Various bug fixes and performance improvements
\ No newline at end of file
web/5.88.7 extension/1.22.2
\ No newline at end of file \ No newline at end of file
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
"@reduxjs/toolkit": "1.9.3", "@reduxjs/toolkit": "1.9.3",
"@svgr/webpack": "8.0.1", "@svgr/webpack": "8.0.1",
"@tamagui/core": "1.125.17", "@tamagui/core": "1.125.17",
"@tanstack/react-query": "5.77.2",
"@types/uuid": "9.0.1", "@types/uuid": "9.0.1",
"@uniswap/analytics-events": "2.42.0", "@uniswap/analytics-events": "2.42.0",
"@uniswap/client-embeddedwallet": "0.0.16", "@uniswap/client-embeddedwallet": "0.0.16",
...@@ -36,7 +35,6 @@ ...@@ -36,7 +35,6 @@
"react-native-reanimated": "3.16.7", "react-native-reanimated": "3.16.7",
"react-native-svg": "15.10.1", "react-native-svg": "15.10.1",
"react-native-web": "0.19.13", "react-native-web": "0.19.13",
"react-player": "2.16.0",
"react-qr-code": "2.0.12", "react-qr-code": "2.0.12",
"react-redux": "8.0.5", "react-redux": "8.0.5",
"react-router-dom": "6.10.0", "react-router-dom": "6.10.0",
......
import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { SharedQueryClient } from 'uniswap/src/data/apiClients/SharedQueryClient'
import { StatsigProviderWrapper } from 'uniswap/src/features/gating/StatsigProviderWrapper' import { StatsigProviderWrapper } from 'uniswap/src/features/gating/StatsigProviderWrapper'
import { StatsigCustomAppValue } from 'uniswap/src/features/gating/constants' import { StatsigCustomAppValue } from 'uniswap/src/features/gating/constants'
import { StatsigClient, StatsigUser } from 'uniswap/src/features/gating/sdk/statsig' import { StatsigClient, StatsigUser } from 'uniswap/src/features/gating/sdk/statsig'
import { statsigBaseConfig } from 'uniswap/src/features/gating/statsigBaseConfig' import { statsigBaseConfig } from 'uniswap/src/features/gating/statsigBaseConfig'
import { initializeDatadog } from 'uniswap/src/utils/datadog' import { initializeDatadog } from 'uniswap/src/utils/datadog'
import { getUniqueId } from 'utilities/src/device/uniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { uniqueIdQuery } from 'utilities/src/device/uniqueIdQuery'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
function makeStatsigUser(userID: string): StatsigUser { async function getStatsigUser(): Promise<StatsigUser> {
return { return {
userID, userID: await getUniqueId(),
appVersion: process.env.VERSION, appVersion: process.env.VERSION,
custom: { custom: {
app: StatsigCustomAppValue.Extension, app: StatsigCustomAppValue.Extension,
...@@ -28,7 +26,7 @@ export function ExtensionStatsigProvider({ ...@@ -28,7 +26,7 @@ export function ExtensionStatsigProvider({
children: React.ReactNode children: React.ReactNode
appName: string appName: string
}): JSX.Element { }): JSX.Element {
const { data: uniqueId } = useQuery(uniqueIdQuery(), SharedQueryClient) const { data: storedUser } = useAsyncData(getStatsigUser)
const [initFinished, setInitFinished] = useState(false) const [initFinished, setInitFinished] = useState(false)
const [user, setUser] = useState<StatsigUser>({ const [user, setUser] = useState<StatsigUser>({
userID: undefined, userID: undefined,
...@@ -39,10 +37,10 @@ export function ExtensionStatsigProvider({ ...@@ -39,10 +37,10 @@ export function ExtensionStatsigProvider({
}) })
useEffect(() => { useEffect(() => {
if (uniqueId && initFinished) { if (storedUser && initFinished) {
setUser(makeStatsigUser(uniqueId)) setUser(storedUser)
} }
}, [uniqueId, initFinished]) }, [storedUser, initFinished])
const onStatsigInit = (): void => { const onStatsigInit = (): void => {
setInitFinished(true) setInitFinished(true)
...@@ -57,8 +55,7 @@ export function ExtensionStatsigProvider({ ...@@ -57,8 +55,7 @@ export function ExtensionStatsigProvider({
} }
export async function initStatSigForBrowserScripts(): Promise<void> { export async function initStatSigForBrowserScripts(): Promise<void> {
const uniqueId = await getUniqueId() const statsigClient = new StatsigClient(config.statsigApiKey, await getStatsigUser(), statsigBaseConfig)
const statsigClient = new StatsigClient(config.statsigApiKey, makeStatsigUser(uniqueId), statsigBaseConfig)
await statsigClient.initializeAsync().catch((error) => { await statsigClient.initializeAsync().catch((error) => {
logger.error(error, { logger.error(error, {
tags: { file: 'StatsigProvider.tsx', function: 'initStatSigForBrowserScripts' }, tags: { file: 'StatsigProvider.tsx', function: 'initStatSigForBrowserScripts' },
......
...@@ -2,7 +2,6 @@ import { useApolloClient } from '@apollo/client' ...@@ -2,7 +2,6 @@ import { useApolloClient } from '@apollo/client'
import { SharedEventName } from '@uniswap/analytics-events' import { SharedEventName } from '@uniswap/analytics-events'
import { memo, useCallback, useEffect, useState } from 'react' import { memo, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ReactPlayer from 'react-player'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { ActivityTab } from 'src/app/components/tabs/ActivityTab' import { ActivityTab } from 'src/app/components/tabs/ActivityTab'
import { NftsTab } from 'src/app/components/tabs/NftsTab' import { NftsTab } from 'src/app/components/tabs/NftsTab'
...@@ -44,19 +43,19 @@ import { setSmartWalletConsent } from 'wallet/src/features/wallet/slice' ...@@ -44,19 +43,19 @@ import { setSmartWalletConsent } from 'wallet/src/features/wallet/slice'
const MemoizedVideo = memo(() => ( const MemoizedVideo = memo(() => (
<Flex borderRadius="$rounded12" overflow="hidden" height="auto" maxWidth="100%" aspectRatio="16 / 9"> <Flex borderRadius="$rounded12" overflow="hidden" height="auto" maxWidth="100%" aspectRatio="16 / 9">
<ReactPlayer <video
url={SMART_WALLET_UPGRADE_VIDEO} src={SMART_WALLET_UPGRADE_VIDEO}
width="100%"
height="100%"
playing={true}
muted={true}
style={{ style={{
width: '100%',
height: '100%',
objectFit: 'cover', objectFit: 'cover',
}} }}
fallback={undefined} autoPlay
muted
/> />
</Flex> </Flex>
)) ))
MemoizedVideo.displayName = 'MemoizedVideo' MemoizedVideo.displayName = 'MemoizedVideo'
export const HomeScreen = memo(function _HomeScreen(): JSX.Element { export const HomeScreen = memo(function _HomeScreen(): JSX.Element {
......
...@@ -7,6 +7,8 @@ import { navigate } from 'src/app/navigation/state' ...@@ -7,6 +7,8 @@ import { navigate } from 'src/app/navigation/state'
import { Flex, Text, getTokenValue, useMedia } from 'ui/src' import { Flex, Text, getTokenValue, useMedia } from 'ui/src'
import { ArrowDownCircle, Bank, CoinConvert, SendAction } from 'ui/src/components/icons' import { ArrowDownCircle, Bank, CoinConvert, SendAction } from 'ui/src/components/icons'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal'
...@@ -72,6 +74,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J ...@@ -72,6 +74,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
const { t } = useTranslation() const { t } = useTranslation()
const media = useMedia() const media = useMedia()
const { isTestnetModeEnabled } = useEnabledChains() const { isTestnetModeEnabled } = useEnabledChains()
const isFiatOffRampEnabled = useFeatureFlag(FeatureFlags.FiatOffRamp)
const onSendClick = (): void => { const onSendClick = (): void => {
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, { sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
...@@ -123,7 +126,11 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J ...@@ -123,7 +126,11 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
/> />
<Flex row shrink gap="$spacing8" width={isGrid ? '100%' : '50%'}> <Flex row shrink gap="$spacing8" width={isGrid ? '100%' : '50%'}>
<ActionButton Icon={<CoinConvert />} label={t('home.label.swap')} onClick={onSwapClick} /> <ActionButton Icon={<CoinConvert />} label={t('home.label.swap')} onClick={onSwapClick} />
<ActionButton Icon={<Bank />} label={t('home.label.buy')} onClick={onBuyClick} /> <ActionButton
Icon={<Bank />}
label={isFiatOffRampEnabled ? t('home.label.for') : t('home.label.buy')}
onClick={onBuyClick}
/>
</Flex> </Flex>
<Flex row shrink gap="$spacing8" width={isGrid ? '100%' : '50%'}> <Flex row shrink gap="$spacing8" width={isGrid ? '100%' : '50%'}>
<ActionButton Icon={<SendAction />} label={t('home.label.send')} onClick={onSendClick} /> <ActionButton Icon={<SendAction />} label={t('home.label.send')} onClick={onSendClick} />
......
import { useQuery } from '@tanstack/react-query'
import { ComponentProps, useMemo, useState } from 'react' import { ComponentProps, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { SelectWalletsSkeleton } from 'src/app/components/loading/SelectWalletSkeleton' import { SelectWalletsSkeleton } from 'src/app/components/loading/SelectWalletSkeleton'
...@@ -14,9 +13,7 @@ import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' ...@@ -14,9 +13,7 @@ import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension' import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension'
import { openUri } from 'uniswap/src/utils/linking' import { openUri } from 'uniswap/src/utils/linking'
import { useEvent } from 'utilities/src/react/hooks' import { useAsyncData, useEvent } from 'utilities/src/react/hooks'
import { ReactQueryCacheKey } from 'utilities/src/reactQuery/cache'
import { queryWithoutCache } from 'utilities/src/reactQuery/queryOptions'
import WalletPreviewCard from 'wallet/src/components/WalletPreviewCard/WalletPreviewCard' import WalletPreviewCard from 'wallet/src/components/WalletPreviewCard/WalletPreviewCard'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext' import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
import { useImportableAccounts } from 'wallet/src/features/onboarding/hooks/useImportableAccounts' import { useImportableAccounts } from 'wallet/src/features/onboarding/hooks/useImportableAccounts'
...@@ -31,9 +28,7 @@ export function SelectWallets({ flow }: { flow: ExtensionOnboardingFlow }): JSX. ...@@ -31,9 +28,7 @@ export function SelectWallets({ flow }: { flow: ExtensionOnboardingFlow }): JSX.
const { goToNextStep, goToPreviousStep } = useOnboardingSteps() const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const { generateAccountsAndImportAddresses, getGeneratedAddresses } = useOnboardingContext() const { generateAccountsAndImportAddresses, getGeneratedAddresses } = useOnboardingContext()
const { data: generatedAddresses } = useQuery( const { data: generatedAddresses } = useAsyncData(getGeneratedAddresses)
queryWithoutCache({ queryFn: getGeneratedAddresses, queryKey: [ReactQueryCacheKey.GeneratedAddresses] }),
)
const { importableAccounts, isLoading, showError, refetch } = useImportableAccounts(generatedAddresses) const { importableAccounts, isLoading, showError, refetch } = useImportableAccounts(generatedAddresses)
......
import { useQuery } from '@tanstack/react-query/build/modern/useQuery'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
...@@ -13,9 +12,9 @@ import { useExtensionNavigation } from 'src/app/navigation/utils' ...@@ -13,9 +12,9 @@ import { useExtensionNavigation } from 'src/app/navigation/utils'
import { Checkbox, Flex, SpinningLoader, Text, TouchableArea } from 'ui/src' import { Checkbox, Flex, SpinningLoader, Text, TouchableArea } from 'ui/src'
import { AlertTriangleFilled, FileListCheck, FileListLock } from 'ui/src/components/icons' import { AlertTriangleFilled, FileListCheck, FileListLock } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { useEvent } from 'utilities/src/react/hooks' import { useAsyncData, useEvent } from 'utilities/src/react/hooks'
import { useBooleanState } from 'utilities/src/react/useBooleanState' import { useBooleanState } from 'utilities/src/react/useBooleanState'
import { mnemonicUnlockedQuery } from 'wallet/src/features/wallet/Keyring/queries' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { EditAccountAction, editAccountActions } from 'wallet/src/features/wallet/accounts/editAccountSaga' import { EditAccountAction, editAccountActions } from 'wallet/src/features/wallet/accounts/editAccountSaga'
import { BackupType } from 'wallet/src/features/wallet/accounts/types' import { BackupType } from 'wallet/src/features/wallet/accounts/types'
import { hasBackup } from 'wallet/src/features/wallet/accounts/utils' import { hasBackup } from 'wallet/src/features/wallet/accounts/utils'
...@@ -154,7 +153,9 @@ function RecoveryPhraseVerificationStep({ ...@@ -154,7 +153,9 @@ function RecoveryPhraseVerificationStep({
const [hasError, setHasError] = useState(false) const [hasError, setHasError] = useState(false)
const [numberOfWordsVerified, setNumberOfWordsVerified] = useState(0) const [numberOfWordsVerified, setNumberOfWordsVerified] = useState(0)
const { data: mnemonic, error } = useQuery(mnemonicUnlockedQuery(mnemonicId)) const { data: mnemonic, error } = useAsyncData(
useCallback(async () => Keyring.retrieveMnemonicUnlocked(mnemonicId), [mnemonicId]),
)
if (error) { if (error) {
// This should never happen. We can't recover from a missing mnemonic. // This should never happen. We can't recover from a missing mnemonic.
......
import { useQuery } from '@tanstack/react-query' import { useCallback, useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { LayoutChangeEvent } from 'react-native' import { LayoutChangeEvent } from 'react-native'
import { CopyButton } from 'src/app/components/buttons/CopyButton' import { CopyButton } from 'src/app/components/buttons/CopyButton'
import { Flex, Separator, Text } from 'ui/src' import { Flex, Separator, Text } from 'ui/src'
...@@ -8,7 +7,8 @@ import { WalletEventName } from 'uniswap/src/features/telemetry/constants' ...@@ -8,7 +7,8 @@ import { WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { setClipboard } from 'uniswap/src/utils/clipboard' import { setClipboard } from 'uniswap/src/utils/clipboard'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { mnemonicUnlockedQuery } from 'wallet/src/features/wallet/Keyring/queries' import { useAsyncData } from 'utilities/src/react/hooks'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
function SeedPhraseColumnGroup({ recoveryPhraseArray }: { recoveryPhraseArray: string[] }): JSX.Element { function SeedPhraseColumnGroup({ recoveryPhraseArray }: { recoveryPhraseArray: string[] }): JSX.Element {
const [largestIndexWidth, setLargestIndexWidth] = useState(0) const [largestIndexWidth, setLargestIndexWidth] = useState(0)
...@@ -95,7 +95,9 @@ function SeedPhraseWord({ ...@@ -95,7 +95,9 @@ function SeedPhraseWord({
export function SeedPhraseDisplay({ mnemonicId }: { mnemonicId: string }): JSX.Element { export function SeedPhraseDisplay({ mnemonicId }: { mnemonicId: string }): JSX.Element {
const placeholderWordArrayLength = 12 const placeholderWordArrayLength = 12
const { data: recoveryPhraseString } = useQuery(mnemonicUnlockedQuery(mnemonicId)) const recoveryPhraseString = useAsyncData(
useCallback(async () => Keyring.retrieveMnemonicUnlocked(mnemonicId), [mnemonicId]),
).data
const recoveryPhraseArray = recoveryPhraseString?.split(' ') ?? Array(placeholderWordArrayLength).fill('') const recoveryPhraseArray = recoveryPhraseString?.split(' ') ?? Array(placeholderWordArrayLength).fill('')
const onCopyPress = async (): Promise<void> => { const onCopyPress = async (): Promise<void> => {
......
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader' import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { SCREEN_ITEM_HORIZONTAL_PAD } from 'src/app/constants'
import { SettingsItemWithDropdown } from 'src/app/features/settings/SettingsItemWithDropdown' import { SettingsItemWithDropdown } from 'src/app/features/settings/SettingsItemWithDropdown'
import ThemeToggle from 'src/app/features/settings/ThemeToggle' import ThemeToggle from 'src/app/features/settings/ThemeToggle'
import { SettingsItem } from 'src/app/features/settings/components/SettingsItem'
import { SettingsSection } from 'src/app/features/settings/components/SettingsSection'
import { SettingsToggleRow } from 'src/app/features/settings/components/SettingsToggleRow'
import { AppRoutes, SettingsRoutes } from 'src/app/navigation/constants' import { AppRoutes, SettingsRoutes } from 'src/app/navigation/constants'
import { useExtensionNavigation } from 'src/app/navigation/utils' import { useExtensionNavigation } from 'src/app/navigation/utils'
import { getIsDefaultProviderFromStorage, setIsDefaultProviderToStorage } from 'src/app/utils/provider' import { getIsDefaultProviderFromStorage, setIsDefaultProviderToStorage } from 'src/app/utils/provider'
import { Button, Flex, ScrollView, Text } from 'ui/src' import {
Button,
ColorTokens,
Flex,
GeneratedIcon,
ScrollView,
Switch,
Text,
TouchableArea,
useSporeColors,
} from 'ui/src'
import { import {
ArrowUpRight, ArrowUpRight,
Chart, Chart,
...@@ -23,10 +32,12 @@ import { ...@@ -23,10 +32,12 @@ import {
LineChartDots, LineChartDots,
Lock, Lock,
Passkey, Passkey,
RotatableChevron,
Settings, Settings,
Sliders, Sliders,
Wrench, Wrench,
} from 'ui/src/components/icons' } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { resetUniswapBehaviorHistory } from 'uniswap/src/features/behaviorHistory/slice' import { resetUniswapBehaviorHistory } from 'uniswap/src/features/behaviorHistory/slice'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
...@@ -322,3 +333,124 @@ export function SettingsScreen(): JSX.Element { ...@@ -322,3 +333,124 @@ export function SettingsScreen(): JSX.Element {
</Trace> </Trace>
) )
} }
function SettingsItem({
Icon,
title,
onPress,
iconProps,
themeProps,
url,
count,
hideChevron = false,
RightIcon,
}: {
Icon: GeneratedIcon
title: string
hideChevron?: boolean
RightIcon?: GeneratedIcon
onPress?: () => void
iconProps?: { strokeWidth?: number }
// TODO: do this with a wrapping Theme, "detrimental" wasn't working
themeProps?: { color?: string; hoverColor?: string }
url?: string
count?: number
}): JSX.Element {
const colors = useSporeColors()
const hoverColor = themeProps?.hoverColor ?? colors.surface2.val
const content = (
<TouchableArea
alignItems="center"
borderRadius="$rounded12"
flexDirection="row"
flexGrow={1}
gap="$spacing12"
hoverStyle={{
backgroundColor: hoverColor as ColorTokens,
}}
justifyContent="space-between"
px="$spacing12"
py="$spacing8"
onPress={onPress}
>
<Flex row justifyContent="space-between" flexGrow={1}>
<Flex row gap="$spacing12">
<Icon
color={themeProps?.color ?? '$neutral2'}
size="$icon.24"
strokeWidth={iconProps?.strokeWidth ?? undefined}
/>
<Text style={{ color: themeProps?.color ?? colors.neutral1.val }} variant="subheading2">
{title}
</Text>
</Flex>
{count !== undefined && (
<Text alignSelf="center" color="$neutral2" variant="subheading2">
{count}
</Text>
)}
</Flex>
{RightIcon ? (
<RightIcon color="$neutral3" size="$icon.24" strokeWidth={iconProps?.strokeWidth ?? undefined} />
) : (
!hideChevron && (
<RotatableChevron color="$neutral3" direction="end" height={iconSizes.icon20} width={iconSizes.icon20} />
)
)}
</TouchableArea>
)
if (url) {
return (
<Link style={{ textDecoration: 'none' }} target="_blank" to={url}>
{content}
</Link>
)
}
return content
}
function SettingsToggleRow({
Icon,
title,
checked,
disabled,
onCheckedChange,
}: {
title: string
Icon: GeneratedIcon
checked: boolean
disabled?: boolean
onCheckedChange: (checked: boolean) => void
}): JSX.Element {
return (
<Flex
alignItems="center"
flexDirection="row"
gap="$spacing16"
justifyContent="space-between"
px={SCREEN_ITEM_HORIZONTAL_PAD}
py="$spacing4"
>
<Flex row gap="$spacing12">
<Icon color="$neutral2" size="$icon.24" />
<Text>{title}</Text>
</Flex>
<Switch checked={checked} variant="branded" disabled={disabled} onCheckedChange={onCheckedChange} />
</Flex>
)
}
function SettingsSection({ title, children }: { title: string; children: JSX.Element | JSX.Element[] }): JSX.Element {
return (
<Flex gap="$spacing4">
<Text color="$neutral2" px={SCREEN_ITEM_HORIZONTAL_PAD} variant="subheading2">
{title}
</Text>
{children}
</Flex>
)
}
import { Link } from 'react-router-dom'
import { ColorTokens, Flex, GeneratedIcon, Text, TouchableArea, useSporeColors } from 'ui/src'
import { RotatableChevron } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
export function SettingsItem({
Icon,
title,
onPress,
iconProps,
themeProps,
url,
count,
hideChevron = false,
RightIcon,
}: {
Icon: GeneratedIcon
title: string
hideChevron?: boolean
RightIcon?: GeneratedIcon
onPress?: () => void
iconProps?: { strokeWidth?: number }
// TODO: do this with a wrapping Theme, "detrimental" wasn't working
themeProps?: { color?: string; hoverColor?: string }
url?: string
count?: number
}): JSX.Element {
const colors = useSporeColors()
const hoverColor = themeProps?.hoverColor ?? colors.surface2.val
const content = (
<TouchableArea
alignItems="center"
borderRadius="$rounded12"
flexDirection="row"
flexGrow={1}
gap="$spacing12"
hoverStyle={{
backgroundColor: hoverColor as ColorTokens,
}}
justifyContent="space-between"
px="$spacing12"
py="$spacing8"
onPress={onPress}
>
<Flex row justifyContent="space-between" flexGrow={1}>
<Flex row gap="$spacing12">
<Icon
color={themeProps?.color ?? '$neutral2'}
size="$icon.24"
strokeWidth={iconProps?.strokeWidth ?? undefined}
/>
<Text style={{ color: themeProps?.color ?? colors.neutral1.val }} variant="subheading2">
{title}
</Text>
</Flex>
{count !== undefined && (
<Text alignSelf="center" color="$neutral2" variant="subheading2">
{count}
</Text>
)}
</Flex>
{RightIcon ? (
<RightIcon color="$neutral3" size="$icon.24" strokeWidth={iconProps?.strokeWidth ?? undefined} />
) : (
!hideChevron && (
<RotatableChevron color="$neutral3" direction="end" height={iconSizes.icon20} width={iconSizes.icon20} />
)
)}
</TouchableArea>
)
if (url) {
return (
<Link style={{ textDecoration: 'none' }} target="_blank" to={url}>
{content}
</Link>
)
}
return content
}
import { SCREEN_ITEM_HORIZONTAL_PAD } from 'src/app/constants'
import { Flex, Text } from 'ui/src'
export function SettingsSection({
title,
children,
}: {
title: string
children: JSX.Element | JSX.Element[]
}): JSX.Element {
return (
<Flex gap="$spacing4">
<Text color="$neutral2" px={SCREEN_ITEM_HORIZONTAL_PAD} variant="subheading2">
{title}
</Text>
{children}
</Flex>
)
}
import { SCREEN_ITEM_HORIZONTAL_PAD } from 'src/app/constants'
import { Flex, GeneratedIcon, Switch, Text } from 'ui/src'
export function SettingsToggleRow({
Icon,
title,
checked,
disabled,
onCheckedChange,
}: {
title: string
Icon: GeneratedIcon
checked: boolean
disabled?: boolean
onCheckedChange: (checked: boolean) => void
}): JSX.Element {
return (
<Flex
alignItems="center"
flexDirection="row"
gap="$spacing16"
justifyContent="space-between"
px={SCREEN_ITEM_HORIZONTAL_PAD}
py="$spacing4"
>
<Flex row gap="$spacing12">
<Icon color="$neutral2" size="$icon.24" />
<Text>{title}</Text>
</Flex>
<Switch checked={checked} variant="branded" disabled={disabled} onCheckedChange={onCheckedChange} />
</Flex>
)
}
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring'
import { ENCRYPTION_KEY_STORAGE_KEY, PersistedStorage } from 'wallet/src/utils/persistedStorage' import { ENCRYPTION_KEY_STORAGE_KEY, PersistedStorage } from 'wallet/src/utils/persistedStorage'
...@@ -52,9 +53,7 @@ export function useIsWalletUnlocked(): boolean | null { ...@@ -52,9 +53,7 @@ export function useIsWalletUnlocked(): boolean | null {
} }
}, [checkWalletStatus]) }, [checkWalletStatus])
useEffect(() => { useAsyncData(checkWalletStatus)
checkWalletStatus()
}, [checkWalletStatus])
return isUnlocked return isUnlocked
} }
import { useMutation } from '@tanstack/react-query' import { useCallback, useMemo, useRef } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { NavigationType, Outlet, ScrollRestoration, useLocation } from 'react-router-dom' import { NavigationType, Outlet, ScrollRestoration, useLocation } from 'react-router-dom'
import { SmartWalletNudgeModals } from 'src/app/components/modals/SmartWalletNudgeModals' import { SmartWalletNudgeModals } from 'src/app/components/modals/SmartWalletNudgeModals'
...@@ -19,7 +18,7 @@ import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector' ...@@ -19,7 +18,7 @@ import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector'
import { AnimatePresence, Flex, SpinningLoader, styled } from 'ui/src' import { AnimatePresence, Flex, SpinningLoader, styled } from 'ui/src'
import { TestnetModeBanner } from 'uniswap/src/components/banners/TestnetModeBanner' import { TestnetModeBanner } from 'uniswap/src/components/banners/TestnetModeBanner'
import { useIsChromeWindowFocusedWithTimeout } from 'uniswap/src/extension/useIsChromeWindowFocused' import { useIsChromeWindowFocusedWithTimeout } from 'uniswap/src/extension/useIsChromeWindowFocused'
import { useEvent, usePrevious } from 'utilities/src/react/hooks' import { useAsyncData, usePrevious } from 'utilities/src/react/hooks'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater' import { TransactionHistoryUpdater } from 'wallet/src/features/transactions/TransactionHistoryUpdater'
import { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext' import { WalletUniswapProvider } from 'wallet/src/features/transactions/contexts/WalletUniswapContext'
...@@ -231,27 +230,17 @@ function LoggedOut(): JSX.Element { ...@@ -231,27 +230,17 @@ function LoggedOut(): JSX.Element {
const isOnboarded = useSelector(isOnboardedSelector) const isOnboarded = useSelector(isOnboardedSelector)
const didOpenOnboarding = useRef(false) const didOpenOnboarding = useRef(false)
const focusOrCreateOnboardingTabMutation = useMutation({ const handleOnboarding = useCallback(async () => {
onMutate: () => { if (!isOnboarded && !didOpenOnboarding.current) {
// We keep track of this to avoid opening the onboarding page multiple times if this component remounts. // We keep track of this to avoid opening the onboarding page multiple times if this component remounts.
didOpenOnboarding.current = true didOpenOnboarding.current = true
}, await focusOrCreateOnboardingTab()
mutationFn: () => {
return focusOrCreateOnboardingTab()
},
onSuccess: () => {
// Automatically close the pop up after focusing on the onboarding tab. // Automatically close the pop up after focusing on the onboarding tab.
window.close() window.close()
},
})
const focusOrCreateOnboardingTabEvent = useEvent(focusOrCreateOnboardingTabMutation.mutate)
useEffect(() => {
if (!focusOrCreateOnboardingTabMutation.isPending && !isOnboarded && !didOpenOnboarding.current) {
focusOrCreateOnboardingTabEvent()
} }
}, [focusOrCreateOnboardingTabEvent, isOnboarded, focusOrCreateOnboardingTabMutation.isPending]) }, [isOnboarded])
useAsyncData(handleOnboarding)
// If the user has not onboarded, we render nothing and let the `useEffect` above automatically close the popup. // If the user has not onboarded, we render nothing and let the `useEffect` above automatically close the popup.
// We could consider showing a loading spinner while the popup is being closed. // We could consider showing a loading spinner while the popup is being closed.
......
...@@ -4,7 +4,7 @@ import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, or ...@@ -4,7 +4,7 @@ import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, or
import { EXTENSION_ORIGIN_APPLICATION } from 'src/app/version' import { EXTENSION_ORIGIN_APPLICATION } from 'src/app/version'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { getUniqueId } from 'utilities/src/device/uniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { ApplicationTransport } from 'utilities/src/telemetry/analytics/ApplicationTransport' import { ApplicationTransport } from 'utilities/src/telemetry/analytics/ApplicationTransport'
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { analytics, getAnalyticsAtomDirect } from 'utilities/src/telemetry/analytics/analytics' import { analytics, getAnalyticsAtomDirect } from 'utilities/src/telemetry/analytics/analytics'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Uniswap Extension", "name": "Uniswap Extension",
"description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.", "description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.",
"version": "1.23.0", "version": "1.22.2",
"minimum_chrome_version": "116", "minimum_chrome_version": "116",
"icons": { "icons": {
"16": "assets/icon16.png", "16": "assets/icon16.png",
......
...@@ -24,7 +24,6 @@ import { ...@@ -24,7 +24,6 @@ import {
v20Schema, v20Schema,
v21Schema, v21Schema,
v22Schema, v22Schema,
v23Schema,
v2Schema, v2Schema,
v3Schema, v3Schema,
v4Schema, v4Schema,
...@@ -58,7 +57,6 @@ import { ...@@ -58,7 +57,6 @@ import {
testAddCreatedOnboardingRedesignAccount, testAddCreatedOnboardingRedesignAccount,
testAddedHapticSetting, testAddedHapticSetting,
testDeleteWelcomeWalletCard, testDeleteWelcomeWalletCard,
testMoveHapticsToUserSettings,
testMoveTokenAndNFTVisibility, testMoveTokenAndNFTVisibility,
testMovedCurrencySetting, testMovedCurrencySetting,
testMovedLanguageSetting, testMovedLanguageSetting,
...@@ -322,8 +320,4 @@ describe('Redux state migrations', () => { ...@@ -322,8 +320,4 @@ describe('Redux state migrations', () => {
it('migrates from v22 to v23', () => { it('migrates from v22 to v23', () => {
testMigrateUnknownBackupAccountsToMaybeManualBackup(migrations[23], v22Schema) testMigrateUnknownBackupAccountsToMaybeManualBackup(migrations[23], v22Schema)
}) })
it('migrates from v23 to v24', () => {
testMoveHapticsToUserSettings(migrations[24], v23Schema)
})
}) })
...@@ -21,7 +21,6 @@ import { ...@@ -21,7 +21,6 @@ import {
deleteWelcomeWalletCardBehaviorHistory, deleteWelcomeWalletCardBehaviorHistory,
moveCurrencySetting, moveCurrencySetting,
moveDismissedTokenWarnings, moveDismissedTokenWarnings,
moveHapticsToUserSettings,
moveLanguageSetting, moveLanguageSetting,
moveTokenAndNFTVisibility, moveTokenAndNFTVisibility,
moveUserSettings, moveUserSettings,
...@@ -56,7 +55,6 @@ export const migrations = { ...@@ -56,7 +55,6 @@ export const migrations = {
21: migratePendingDappRequestsToRecord, 21: migratePendingDappRequestsToRecord,
22: addBatchedTransactions, 22: addBatchedTransactions,
23: migrateUnknownBackupAccountsToMaybeManualBackup, 23: migrateUnknownBackupAccountsToMaybeManualBackup,
24: moveHapticsToUserSettings,
} }
export const EXTENSION_STATE_VERSION = 24 export const EXTENSION_STATE_VERSION = 23
...@@ -254,20 +254,5 @@ export const v22Schema = { ...@@ -254,20 +254,5 @@ export const v22Schema = {
batchedTransactions: {}, batchedTransactions: {},
} }
export const v23Schema = v22Schema const v23Schema = v22Schema
export const getSchema = (): typeof v23Schema => v23Schema
const v24SchemaIntermediate = {
...v23Schema,
appearanceSettings: {
...v23Schema.appearanceSettings,
hapticsEnabled: undefined,
},
userSettings: {
...v23Schema.userSettings,
hapticsEnabled: v23Schema.appearanceSettings.hapticsEnabled,
},
}
delete v24SchemaIntermediate.appearanceSettings.hapticsEnabled
const v24Schema = v24SchemaIntermediate
export const getSchema = (): typeof v24Schema => v24Schema
...@@ -37,3 +37,6 @@ env: ...@@ -37,3 +37,6 @@ env:
- waitForAnimationToEnd - waitForAnimationToEnd
- runFlow: biometrics-confirm.yaml - runFlow: biometrics-confirm.yaml
- waitForAnimationToEnd - waitForAnimationToEnd
- tapOn:
id: ${output.testIds.SmartWalletUpgradeModalMaybeLater}
- waitForAnimationToEnd
...@@ -2,5 +2,4 @@ ...@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/monochrome"/>
</adaptive-icon> </adaptive-icon>
...@@ -2,5 +2,4 @@ ...@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/monochrome"/> </adaptive-icon>
</adaptive-icon> \ No newline at end of file
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
"@shopify/react-native-performance-navigation": "3.0.0", "@shopify/react-native-performance-navigation": "3.0.0",
"@shopify/react-native-skia": "1.7.2", "@shopify/react-native-skia": "1.7.2",
"@sparkfabrik/react-native-idfa-aaid": "1.2.0", "@sparkfabrik/react-native-idfa-aaid": "1.2.0",
"@tanstack/react-query": "5.77.2", "@tanstack/react-query": "5.51.16",
"@testing-library/react-hooks": "8.0.1", "@testing-library/react-hooks": "8.0.1",
"@uniswap/analytics": "1.7.0", "@uniswap/analytics": "1.7.0",
"@uniswap/analytics-events": "2.42.0", "@uniswap/analytics-events": "2.42.0",
...@@ -112,6 +112,7 @@ ...@@ -112,6 +112,7 @@
"expo-blur": "14.0.3", "expo-blur": "14.0.3",
"expo-camera": "16.0.18", "expo-camera": "16.0.18",
"expo-clipboard": "7.0.1", "expo-clipboard": "7.0.1",
"expo-haptics": "14.0.1",
"expo-linear-gradient": "14.0.2", "expo-linear-gradient": "14.0.2",
"expo-linking": "7.0.5", "expo-linking": "7.0.5",
"expo-local-authentication": "15.0.2", "expo-local-authentication": "15.0.2",
......
#!/bin/bash #!/bin/bash
MAX_SIZE=23.66 MAX_SIZE=24
MAX_BUFFER=0.5 MAX_BUFFER=0.5
# Check OS type and use appropriate stat command # Check OS type and use appropriate stat command
......
...@@ -89,7 +89,6 @@ import { ...@@ -89,7 +89,6 @@ import {
v84Schema, v84Schema,
v85Schema, v85Schema,
v86Schema, v86Schema,
v87Schema,
v8Schema, v8Schema,
v9Schema, v9Schema,
} from 'src/app/schema' } from 'src/app/schema'
...@@ -136,7 +135,6 @@ import { ...@@ -136,7 +135,6 @@ import {
testMovedLanguageSetting, testMovedLanguageSetting,
testMovedTokenWarnings, testMovedTokenWarnings,
testMovedUserSettings, testMovedUserSettings,
testMoveHapticsToUserSettings,
testMoveTokenAndNFTVisibility, testMoveTokenAndNFTVisibility,
testRemoveCreatedOnboardingRedesignAccount, testRemoveCreatedOnboardingRedesignAccount,
testRemoveHoldToSwap, testRemoveHoldToSwap,
...@@ -1706,8 +1704,4 @@ describe('Redux state migrations', () => { ...@@ -1706,8 +1704,4 @@ describe('Redux state migrations', () => {
}, },
}) })
}) })
it('migrates from v87 to v88', () => {
testMoveHapticsToUserSettings(migrations[88], v87Schema)
})
}) })
...@@ -34,7 +34,6 @@ import { ...@@ -34,7 +34,6 @@ import {
deleteWelcomeWalletCardBehaviorHistory, deleteWelcomeWalletCardBehaviorHistory,
moveCurrencySetting, moveCurrencySetting,
moveDismissedTokenWarnings, moveDismissedTokenWarnings,
moveHapticsToUserSettings,
moveLanguageSetting, moveLanguageSetting,
moveTokenAndNFTVisibility, moveTokenAndNFTVisibility,
moveUserSettings, moveUserSettings,
...@@ -1054,8 +1053,6 @@ export const migrations = { ...@@ -1054,8 +1053,6 @@ export const migrations = {
transactions: newTransactionState, transactions: newTransactionState,
} }
}, },
88: moveHapticsToUserSettings,
} }
export const MOBILE_STATE_VERSION = 88 export const MOBILE_STATE_VERSION = 87
...@@ -9,8 +9,8 @@ import { useBiometricPrompt } from 'src/features/biometricsSettings/hooks' ...@@ -9,8 +9,8 @@ import { useBiometricPrompt } from 'src/features/biometricsSettings/hooks'
import { closeModal } from 'src/features/modals/modalSlice' import { closeModal } from 'src/features/modals/modalSlice'
import { selectModalState } from 'src/features/modals/selectModalState' import { selectModalState } from 'src/features/modals/selectModalState'
import { useWalletRestore } from 'src/features/wallet/useWalletRestore' import { useWalletRestore } from 'src/features/wallet/useWalletRestore'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { updateSwapStartTimestamp } from 'uniswap/src/features/timing/slice' import { updateSwapStartTimestamp } from 'uniswap/src/features/timing/slice'
import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/form/hooks/useSwapPrefilledState' import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/form/hooks/useSwapPrefilledState'
......
...@@ -16,13 +16,13 @@ import { useDispatch, useSelector } from 'react-redux' ...@@ -16,13 +16,13 @@ import { useDispatch, useSelector } from 'react-redux'
import { useAppStackNavigation } from 'src/app/navigation/types' import { useAppStackNavigation } from 'src/app/navigation/types'
import { pulseAnimation } from 'src/components/buttons/utils' import { pulseAnimation } from 'src/components/buttons/utils'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { Flex, FlexProps, LinearGradient, Text, TouchableArea, useIsDarkMode, useSporeColors } from 'ui/src' import { Flex, FlexProps, LinearGradient, Text, TouchableArea, useIsDarkMode, useSporeColors } from 'ui/src'
import { Search } from 'ui/src/components/icons' import { Search } from 'ui/src/components/icons'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { borderRadii, fonts, opacify } from 'ui/src/theme' import { borderRadii, fonts, opacify } from 'ui/src/theme'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { useHighestBalanceNativeCurrencyId } from 'uniswap/src/features/dataApi/balances' import { useHighestBalanceNativeCurrencyId } from 'uniswap/src/features/dataApi/balances'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { selectFilteredChainIds } from 'uniswap/src/features/transactions/swap/contexts/selectors' import { selectFilteredChainIds } from 'uniswap/src/features/transactions/swap/contexts/selectors'
......
...@@ -4,10 +4,9 @@ import { ...@@ -4,10 +4,9 @@ import {
NavigationContainer as NativeNavigationContainer, NavigationContainer as NativeNavigationContainer,
NavigationContainerRefWithCurrent, NavigationContainerRefWithCurrent,
} from '@react-navigation/native' } from '@react-navigation/native'
import { useMutation } from '@tanstack/react-query'
import { SharedEventName } from '@uniswap/analytics-events' import { SharedEventName } from '@uniswap/analytics-events'
import React, { FC, PropsWithChildren, useEffect, useRef, useState } from 'react' import React, { FC, PropsWithChildren, useCallback, useState } from 'react'
import { EmitterSubscription, Linking } from 'react-native' import { Linking } from 'react-native'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { navigationRef } from 'src/app/navigation/navigationRef' import { navigationRef } from 'src/app/navigation/navigationRef'
import { RootParamList } from 'src/app/navigation/types' import { RootParamList } from 'src/app/navigation/types'
...@@ -20,8 +19,7 @@ import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' ...@@ -20,8 +19,7 @@ import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileNavScreen } from 'uniswap/src/types/screens/mobile' import { MobileNavScreen } from 'uniswap/src/types/screens/mobile'
import { datadogEnabledBuild } from 'utilities/src/environment/constants' import { datadogEnabledBuild } from 'utilities/src/environment/constants'
import { logger } from 'utilities/src/logger/logger' import { useAsyncData } from 'utilities/src/react/hooks'
import { useEvent } from 'utilities/src/react/hooks'
import { sleep } from 'utilities/src/time/timing' import { sleep } from 'utilities/src/time/timing'
interface Props { interface Props {
...@@ -89,47 +87,20 @@ export const NavigationContainer: FC<PropsWithChildren<Props>> = ({ children, on ...@@ -89,47 +87,20 @@ export const NavigationContainer: FC<PropsWithChildren<Props>> = ({ children, on
const useManageDeepLinks = (): void => { const useManageDeepLinks = (): void => {
const dispatch = useDispatch() const dispatch = useDispatch()
const hasRun = useRef(false) const manageDeepLinks = useCallback(async () => {
const urlListener = useRef<EmitterSubscription | undefined>() const url = await Linking.getInitialURL()
if (url) {
const deepLinkMutation = useMutation({ dispatch(openDeepLink({ url, coldStart: true }))
mutationFn: async () => { }
if (hasRun.current) { // we need to set an event listener for deep links, but we don't want to do it immediately on cold start,
return // as then there is a change we dispatch `openDeepLink` action twice if app was lauched by a deep link
} await sleep(2000) // 2000 was chosen imperically
const urlListener = Linking.addEventListener('url', (event: { url: string }) =>
const url = await Linking.getInitialURL() dispatch(openDeepLink({ url: event.url, coldStart: false })),
if (url) { )
dispatch(openDeepLink({ url, coldStart: true }))
}
// we need to set an event listener for deep links, but we don't want to do it immediately on cold start,
// as then there is a change we dispatch `openDeepLink` action twice if app was launched by a deep link
await sleep(2000) // 2000 was chosen empirically
urlListener.current = Linking.addEventListener('url', (event: { url: string }) =>
dispatch(openDeepLink({ url: event.url, coldStart: false })),
)
},
onMutate: () => {
hasRun.current = true
},
onError: (error) => {
logger.error(error, {
tags: {
file: 'NavigationContainer',
function: 'useManageDeepLinks',
},
})
},
})
const deepLinkEvent = useEvent(deepLinkMutation.mutate) return urlListener.remove
}, [dispatch])
useEffect(() => { useAsyncData(manageDeepLinks)
deepLinkEvent()
return () => {
if (urlListener.current) {
urlListener.current.remove()
}
}
}, [deepLinkEvent])
} }
...@@ -679,22 +679,8 @@ export const v86Schema = { ...@@ -679,22 +679,8 @@ export const v86Schema = {
batchedTransactions: {}, batchedTransactions: {},
} }
export const v87Schema = v86Schema const v87Schema = v86Schema
const v88SchemaIntermediate = {
...v87Schema,
appearanceSettings: {
...v87Schema.appearanceSettings,
hapticsEnabled: undefined,
},
userSettings: {
...v87Schema.userSettings,
hapticsEnabled: v87Schema.appearanceSettings.hapticsEnabled,
},
}
delete v88SchemaIntermediate.appearanceSettings.hapticsEnabled
const v88Schema = v88SchemaIntermediate
// TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer // TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer
// export const getSchema = (): RootState => v0Schema // export const getSchema = (): RootState => v0Schema
export const getSchema = (): typeof v88Schema => v88Schema export const getSchema = (): typeof v87Schema => v87Schema
...@@ -11,6 +11,7 @@ import { useLineChartPrice } from 'src/components/PriceExplorer/usePrice' ...@@ -11,6 +11,7 @@ import { useLineChartPrice } from 'src/components/PriceExplorer/usePrice'
import { PriceNumberOfDigits, TokenSpotData, useTokenPriceHistory } from 'src/components/PriceExplorer/usePriceHistory' import { PriceNumberOfDigits, TokenSpotData, useTokenPriceHistory } from 'src/components/PriceExplorer/usePriceHistory'
import { useTokenDetailsContext } from 'src/components/TokenDetails/TokenDetailsContext' import { useTokenDetailsContext } from 'src/components/TokenDetails/TokenDetailsContext'
import { Loader } from 'src/components/loading/loaders' import { Loader } from 'src/components/loading/loaders'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { useIsScreenNavigationReady } from 'src/utils/useIsScreenNavigationReady' import { useIsScreenNavigationReady } from 'src/utils/useIsScreenNavigationReady'
import { Flex, SegmentedControl, Text } from 'ui/src' import { Flex, SegmentedControl, Text } from 'ui/src'
import GraphCurve from 'ui/src/assets/backgrounds/graph-curve.svg' import GraphCurve from 'ui/src/assets/backgrounds/graph-curve.svg'
...@@ -19,7 +20,6 @@ import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__gen ...@@ -19,7 +20,6 @@ import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__gen
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementNameType } from 'uniswap/src/features/telemetry/constants' import { ElementNameType } from 'uniswap/src/features/telemetry/constants'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
......
import { useMutation } from '@tanstack/react-query'
import { LinearGradient } from 'expo-linear-gradient' import { LinearGradient } from 'expo-linear-gradient'
import { ComponentProps, default as React, useCallback, useEffect, useMemo, useRef } from 'react' import { ComponentProps, default as React, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native' import { StyleSheet } from 'react-native'
import { FlatList } from 'react-native-gesture-handler' import { FlatList } from 'react-native-gesture-handler'
...@@ -9,8 +8,7 @@ import { Flex, Text, useSporeColors } from 'ui/src' ...@@ -9,8 +8,7 @@ import { Flex, Text, useSporeColors } from 'ui/src'
import { opacify, spacing } from 'ui/src/theme' import { opacify, spacing } from 'ui/src/theme'
import { PollingInterval } from 'uniswap/src/constants/misc' import { PollingInterval } from 'uniswap/src/constants/misc'
import { AccountType } from 'uniswap/src/features/accounts/types' import { AccountType } from 'uniswap/src/features/accounts/types'
import { logger } from 'utilities/src/logger/logger' import { useAsyncData } from 'utilities/src/react/hooks'
import { useEvent } from 'utilities/src/react/hooks'
import { isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { useAccountListData } from 'wallet/src/features/accounts/useAccountListData' import { useAccountListData } from 'wallet/src/features/accounts/useAccountListData'
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
...@@ -52,7 +50,6 @@ type AccountListItem = ...@@ -52,7 +50,6 @@ type AccountListItem =
export function AccountList({ accounts, onPress, isVisible, onClose }: AccountListProps): JSX.Element { export function AccountList({ accounts, onPress, isVisible, onClose }: AccountListProps): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
const addresses = useMemo(() => accounts.map((a) => a.address), [accounts]) const addresses = useMemo(() => accounts.map((a) => a.address), [accounts])
const hasPollingRun = useRef(false)
const { data, networkStatus, refetch, startPolling, stopPolling } = useAccountListData({ const { data, networkStatus, refetch, startPolling, stopPolling } = useAccountListData({
addresses, addresses,
...@@ -61,35 +58,15 @@ export function AccountList({ accounts, onPress, isVisible, onClose }: AccountLi ...@@ -61,35 +58,15 @@ export function AccountList({ accounts, onPress, isVisible, onClose }: AccountLi
// Only poll account total values when the account list is visible // Only poll account total values when the account list is visible
const controlPolling = useCallback(async () => { const controlPolling = useCallback(async () => {
if (hasPollingRun.current) {
return
}
if (isVisible) { if (isVisible) {
refetch() await refetch()
startPolling(PollingInterval.Fast) startPolling(PollingInterval.Fast)
} else { } else {
stopPolling() stopPolling()
} }
}, [isVisible, refetch, startPolling, stopPolling]) }, [isVisible, refetch, startPolling, stopPolling])
const controlPollingMutation = useMutation({ useAsyncData(controlPolling)
mutationFn: controlPolling,
onMutate: () => {
hasPollingRun.current = true
},
onError: (error) => {
logger.error(error, {
tags: { file: 'AccountList', function: 'controlPolling' },
})
},
})
const controlPollingEvent = useEvent(controlPollingMutation.mutate)
useEffect(() => {
controlPollingEvent()
}, [controlPollingEvent])
const isPortfolioValueLoading = isNonPollingRequestInFlight(networkStatus) const isPortfolioValueLoading = isNonPollingRequestInFlight(networkStatus)
......
...@@ -7,11 +7,11 @@ import Sortable from 'react-native-sortables' ...@@ -7,11 +7,11 @@ import Sortable from 'react-native-sortables'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { FavoriteHeaderRow } from 'src/components/explore/FavoriteHeaderRow' import { FavoriteHeaderRow } from 'src/components/explore/FavoriteHeaderRow'
import FavoriteTokenCard from 'src/components/explore/FavoriteTokenCard' import FavoriteTokenCard from 'src/components/explore/FavoriteTokenCard'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { getTokenValue } from 'ui/src' import { getTokenValue } from 'ui/src'
import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex'
import { selectFavoriteTokens } from 'uniswap/src/features/favorites/selectors' import { selectFavoriteTokens } from 'uniswap/src/features/favorites/selectors'
import { setFavoriteTokens } from 'uniswap/src/features/favorites/slice' import { setFavoriteTokens } from 'uniswap/src/features/favorites/slice'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
const NUM_COLUMNS = 2 const NUM_COLUMNS = 2
......
import { NativeModules } from 'react-native'
interface RNCloudStorageBackupsManager { interface RNCloudStorageBackupsManager {
isCloudStorageAvailable: () => Promise<boolean> isCloudStorageAvailable: () => Promise<boolean>
deleteCloudStorageMnemonicBackup: (mnemonicId: string) => Promise<boolean> deleteCloudStorageMnemonicBackup: (mnemonicId: string) => Promise<boolean>
...@@ -14,6 +12,7 @@ declare module 'react-native' { ...@@ -14,6 +12,7 @@ declare module 'react-native' {
RNCloudStorageBackupsManager: RNCloudStorageBackupsManager RNCloudStorageBackupsManager: RNCloudStorageBackupsManager
} }
} }
import { NativeModules } from 'react-native'
const { RNCloudStorageBackupsManager } = NativeModules const { RNCloudStorageBackupsManager } = NativeModules
......
...@@ -5,7 +5,7 @@ import { config } from 'uniswap/src/config' ...@@ -5,7 +5,7 @@ import { config } from 'uniswap/src/config'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { getFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/gating/hooks' import { getFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/gating/hooks'
import { GQL_QUERIES_TO_REFETCH_ON_TXN_UPDATE } from 'uniswap/src/features/portfolio/portfolioUpdates/constants' import { GQL_QUERIES_TO_REFETCH_ON_TXN_UPDATE } from 'uniswap/src/features/portfolio/portfolioUpdates/constants'
import { getUniqueId } from 'utilities/src/device/uniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isIOS } from 'utilities/src/platform' import { isIOS } from 'utilities/src/platform'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
......
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { SEND_CONTENT_RENDER_DELAY_MS } from 'src/features/send/constants' import { SEND_CONTENT_RENDER_DELAY_MS } from 'src/features/send/constants'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { Flex } from 'ui/src/components/layout/Flex' import { Flex } from 'ui/src/components/layout/Flex'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
import { TransactionModalInnerContainer } from 'uniswap/src/features/transactions/components/TransactionModal/TransactionModal' import { TransactionModalInnerContainer } from 'uniswap/src/features/transactions/components/TransactionModal/TransactionModal'
import { useTransactionModalContext } from 'uniswap/src/features/transactions/components/TransactionModal/TransactionModalContext' import { useTransactionModalContext } from 'uniswap/src/features/transactions/components/TransactionModal/TransactionModalContext'
import { SendReviewDetails } from 'wallet/src/features/transactions/send/SendReviewDetails' import { SendReviewDetails } from 'wallet/src/features/transactions/send/SendReviewDetails'
......
...@@ -4,7 +4,7 @@ import DeviceInfo from 'react-native-device-info' ...@@ -4,7 +4,7 @@ import DeviceInfo from 'react-native-device-info'
import { call, delay, fork, select } from 'typed-redux-saga' import { call, delay, fork, select } from 'typed-redux-saga'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { MobileUserPropertyName } from 'uniswap/src/features/telemetry/user' import { MobileUserPropertyName } from 'uniswap/src/features/telemetry/user'
import { getUniqueId } from 'utilities/src/device/uniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { isAndroid } from 'utilities/src/platform' import { isAndroid } from 'utilities/src/platform'
import { ApplicationTransport } from 'utilities/src/telemetry/analytics/ApplicationTransport' import { ApplicationTransport } from 'utilities/src/telemetry/analytics/ApplicationTransport'
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
......
...@@ -175,9 +175,7 @@ export function* handleGetCapabilities({ ...@@ -175,9 +175,7 @@ export function* handleGetCapabilities({
const detailsMap = delegationStatusResponse.delegationDetails[accountAddress] const detailsMap = delegationStatusResponse.delegationDetails[accountAddress]
if (detailsMap) { if (detailsMap) {
const hasAtLeastOneDelegation = Object.values(detailsMap).some( const hasAtLeastOneDelegation = Object.values(detailsMap).some((details) => !!details.currentDelegationAddress)
(details) => !!details.currentDelegationAddress && !details.isWalletDelegatedToUniswap,
)
hasNoExistingDelegations = !hasAtLeastOneDelegation hasNoExistingDelegations = !hasAtLeastOneDelegation
} }
......
import React, { useState } from 'react' import React, { useState } from 'react'
import { I18nManager, ScrollView } from 'react-native' import { I18nManager, ScrollView } from 'react-native'
import { getUniqueIdSync } from 'react-native-device-info'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { BackButton } from 'src/components/buttons/BackButton' import { BackButton } from 'src/components/buttons/BackButton'
...@@ -14,7 +13,9 @@ import { resetDismissedWarnings } from 'uniswap/src/features/tokens/slice/slice' ...@@ -14,7 +13,9 @@ import { resetDismissedWarnings } from 'uniswap/src/features/tokens/slice/slice'
import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { useAppInsets } from 'uniswap/src/hooks/useAppInsets'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { setClipboard } from 'uniswap/src/utils/clipboard' import { setClipboard } from 'uniswap/src/utils/clipboard'
import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useAsyncData } from 'utilities/src/react/hooks'
import { UniconSampleSheet } from 'wallet/src/components/DevelopmentOnly/UniconSampleSheet' import { UniconSampleSheet } from 'wallet/src/components/DevelopmentOnly/UniconSampleSheet'
import { createOnboardingAccount } from 'wallet/src/features/onboarding/createOnboardingAccount' import { createOnboardingAccount } from 'wallet/src/features/onboarding/createOnboardingAccount'
import { createAccountsActions } from 'wallet/src/features/wallet/create/createAccountsSaga' import { createAccountsActions } from 'wallet/src/features/wallet/create/createAccountsSaga'
...@@ -33,7 +34,7 @@ export function DevScreen(): JSX.Element { ...@@ -33,7 +34,7 @@ export function DevScreen(): JSX.Element {
const activeAccount = useActiveAccount() const activeAccount = useActiveAccount()
const [rtlEnabled, setRTLEnabled] = useState(I18nManager.isRTL) const [rtlEnabled, setRTLEnabled] = useState(I18nManager.isRTL)
const sortedMnemonicAccounts = useSelector(selectSortedSignerMnemonicAccounts) const sortedMnemonicAccounts = useSelector(selectSortedSignerMnemonicAccounts)
const deviceId = getUniqueIdSync() const { data: deviceId } = useAsyncData(getUniqueId)
const onPressResetTokenWarnings = (): void => { const onPressResetTokenWarnings = (): void => {
dispatch(resetDismissedWarnings()) dispatch(resetDismissedWarnings())
......
...@@ -4,13 +4,13 @@ import { useDispatch } from 'react-redux' ...@@ -4,13 +4,13 @@ import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { useOpenReceiveModal } from 'src/features/modals/hooks/useOpenReceiveModal' import { useOpenReceiveModal } from 'src/features/modals/hooks/useOpenReceiveModal'
import { openModal } from 'src/features/modals/modalSlice' import { openModal } from 'src/features/modals/modalSlice'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { ArrowDownCircle, Bank, SendAction } from 'ui/src/components/icons' import { ArrowDownCircle, Bank, SendAction } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName, MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants' import { ElementName, MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants'
......
...@@ -35,36 +35,36 @@ const validateForm = ({ ...@@ -35,36 +35,36 @@ const validateForm = ({
validAddress, validAddress,
name, name,
walletExists, walletExists,
isLoading, loading,
isSmartContractAddress, isSmartContractAddress,
isValidSmartContract, isValidSmartContract,
}: { }: {
validAddress: string | null validAddress: string | null
name: string | null name: string | null
walletExists: boolean walletExists: boolean
isLoading: boolean loading: boolean
isSmartContractAddress: boolean isSmartContractAddress: boolean
isValidSmartContract: boolean isValidSmartContract: boolean
}): boolean => { }): boolean => {
return (!!validAddress || !!name) && !walletExists && !isLoading && (!isSmartContractAddress || isValidSmartContract) return (!!validAddress || !!name) && !walletExists && !loading && (!isSmartContractAddress || isValidSmartContract)
} }
const getErrorText = ({ const getErrorText = ({
walletExists, walletExists,
isSmartContractAddress, isSmartContractAddress,
isLoading, loading,
t, t,
}: { }: {
walletExists: boolean walletExists: boolean
isSmartContractAddress: boolean isSmartContractAddress: boolean
isLoading: boolean loading: boolean
t: TFunction t: TFunction
}): string | undefined => { }): string | undefined => {
if (walletExists) { if (walletExists) {
return t('account.wallet.watch.error.alreadyImported') return t('account.wallet.watch.error.alreadyImported')
} else if (isSmartContractAddress) { } else if (isSmartContractAddress) {
return t('account.wallet.watch.error.smartContract') return t('account.wallet.watch.error.smartContract')
} else if (!isLoading) { } else if (!loading) {
return t('account.wallet.watch.error.notFound') return t('account.wallet.watch.error.notFound')
} }
return undefined return undefined
...@@ -91,7 +91,7 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX ...@@ -91,7 +91,7 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX
autocompleteDomain: !hasSuffixIncluded, autocompleteDomain: !hasSuffixIncluded,
}) })
const validAddress = getValidAddress(normalizedValue, true, false) const validAddress = getValidAddress(normalizedValue, true, false)
const { isSmartContractAddress, loading: isLoading } = useIsSmartContractAddress( const { isSmartContractAddress, loading } = useIsSmartContractAddress(
(validAddress || resolvedAddress) ?? undefined, (validAddress || resolvedAddress) ?? undefined,
defaultChainId, defaultChainId,
) )
...@@ -115,12 +115,12 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX ...@@ -115,12 +115,12 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX
validAddress, validAddress,
name, name,
walletExists, walletExists,
isLoading, loading,
isSmartContractAddress, isSmartContractAddress,
isValidSmartContract, isValidSmartContract,
}) })
const errorText = !isValid ? getErrorText({ walletExists, isSmartContractAddress, isLoading, t }) : undefined const errorText = !isValid ? getErrorText({ walletExists, isSmartContractAddress, loading, t }) : undefined
const onSubmit = useCallback(async () => { const onSubmit = useCallback(async () => {
if (isValid && value) { if (isValid && value) {
......
...@@ -28,6 +28,7 @@ import { ...@@ -28,6 +28,7 @@ import {
} from 'src/features/notifications/hooks/useNotificationOSPermissionsEnabled' } from 'src/features/notifications/hooks/useNotificationOSPermissionsEnabled'
import { useWalletRestore } from 'src/features/wallet/useWalletRestore' import { useWalletRestore } from 'src/features/wallet/useWalletRestore'
import { importFromCloudBackupOption, restoreFromCloudBackupOption } from 'src/screens/Import/constants' import { importFromCloudBackupOption, restoreFromCloudBackupOption } from 'src/screens/Import/constants'
import { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { Flex, IconProps, Text, useSporeColors } from 'ui/src' import { Flex, IconProps, Text, useSporeColors } from 'ui/src'
import { import {
Bell, Bell,
...@@ -60,7 +61,6 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' ...@@ -60,7 +61,6 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks'
import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice'
import { useHapticFeedback } from 'uniswap/src/features/settings/useHapticFeedback/useHapticFeedback'
import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
......
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { ImpactFeedbackStyle, NotificationFeedbackType, impactAsync, notificationAsync } from 'expo-haptics' import { ImpactFeedbackStyle, NotificationFeedbackType, impactAsync, notificationAsync } from 'expo-haptics'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { setHapticsEnabled } from 'uniswap/src/features/settings/slice' import { selectHapticsEnabled, setHapticsUserSettingEnabled } from 'wallet/src/features/appearance/slice'
import {
HapticFeedback, type HapticFeedbackStyle = ImpactFeedbackStyle | NotificationFeedbackType
HapticFeedbackControl,
HapticFeedbackStyle, type HapticFeedback = {
NO_HAPTIC_FEEDBACK, impact: (style?: HapticFeedbackStyle) => Promise<void>
} from 'uniswap/src/features/settings/useHapticFeedback/types' light: () => Promise<void>
import { UniswapState } from 'uniswap/src/state/uniswapReducer' success: () => Promise<void>
}
const NO_HAPTIC_FEEDBACK: HapticFeedback = {
impact: async () => Promise.resolve(),
light: async () => Promise.resolve(),
success: async () => Promise.resolve(),
}
const ENABLED_HAPTIC_FEEDBACK: HapticFeedback = { const ENABLED_HAPTIC_FEEDBACK: HapticFeedback = {
impact: (style?: HapticFeedbackStyle) => { impact: (style?: HapticFeedbackStyle) => {
...@@ -24,13 +30,19 @@ function isImpactFeedbackStyle(style: HapticFeedbackStyle): style is ImpactFeedb ...@@ -24,13 +30,19 @@ function isImpactFeedbackStyle(style: HapticFeedbackStyle): style is ImpactFeedb
return Object.values(ImpactFeedbackStyle).includes(style as ImpactFeedbackStyle) return Object.values(ImpactFeedbackStyle).includes(style as ImpactFeedbackStyle)
} }
interface HapticFeedbackControl {
hapticFeedback: HapticFeedback
hapticsEnabled: boolean
setHapticsEnabled: (willBeEnabled: boolean) => void
}
export function useHapticFeedback(): HapticFeedbackControl { export function useHapticFeedback(): HapticFeedbackControl {
const hapticsEnabled = useSelector((state: UniswapState) => state.userSettings.hapticsEnabled) const hapticsEnabled = useSelector(selectHapticsEnabled)
const dispatch = useDispatch() const dispatch = useDispatch()
const handleSetEnabled = useCallback( const handleSetEnabled = useCallback(
(enabled: boolean): void => { (enabled: boolean): void => {
dispatch(setHapticsEnabled(enabled)) dispatch(setHapticsUserSettingEnabled(enabled))
}, },
[dispatch], [dispatch],
) )
......
...@@ -129,7 +129,7 @@ ...@@ -129,7 +129,7 @@
"@uniswap/eslint-config": "workspace:^", "@uniswap/eslint-config": "workspace:^",
"@vercel/og": "0.5.8", "@vercel/og": "0.5.8",
"@vitejs/plugin-react": "4.4.1", "@vitejs/plugin-react": "4.4.1",
"@wagmi/core": "2.17.2", "@wagmi/core": "2.10.2",
"babel-jest": "29.7.0", "babel-jest": "29.7.0",
"babel-plugin-react-compiler": "19.1.0-rc.2", "babel-plugin-react-compiler": "19.1.0-rc.2",
"browser-cache-mock": "0.1.7", "browser-cache-mock": "0.1.7",
...@@ -211,8 +211,8 @@ ...@@ -211,8 +211,8 @@
"@tamagui/portal": "1.125.17", "@tamagui/portal": "1.125.17",
"@tamagui/react-native-svg": "1.125.17", "@tamagui/react-native-svg": "1.125.17",
"@tanstack/query-sync-storage-persister": "5.75.0", "@tanstack/query-sync-storage-persister": "5.75.0",
"@tanstack/react-query": "5.77.2", "@tanstack/react-query": "5.51.16",
"@tanstack/react-query-persist-client": "5.77.2", "@tanstack/react-query-persist-client": "5.75.2",
"@tanstack/react-table": "8.21.2", "@tanstack/react-table": "8.21.2",
"@types/react-scroll-sync": "0.9.0", "@types/react-scroll-sync": "0.9.0",
"@uniswap/analytics": "1.7.0", "@uniswap/analytics": "1.7.0",
...@@ -295,8 +295,8 @@ ...@@ -295,8 +295,8 @@
"utilities": "workspace:^", "utilities": "workspace:^",
"uuid": "9.0.0", "uuid": "9.0.0",
"video-extensions": "1.2.0", "video-extensions": "1.2.0",
"viem": "2.30.5", "viem": "2.22.9",
"wagmi": "2.15.4", "wagmi": "2.9.3",
"wcag-contrast": "3.0.0", "wcag-contrast": "3.0.0",
"web-vitals": "2.1.4", "web-vitals": "2.1.4",
"xml2js": "0.6.2", "xml2js": "0.6.2",
......
...@@ -126,4 +126,16 @@ ...@@ -126,4 +126,16 @@
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.5</priority> <priority>0.5</priority>
</url> </url>
<url>
<loc>https://app.uniswap.org/positions/create</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
</urlset> </urlset>
This diff is collapsed.
This diff is collapsed.
...@@ -9,7 +9,4 @@ ...@@ -9,7 +9,4 @@
<sitemap> <sitemap>
<loc>https://app.uniswap.org/pools-sitemap.xml</loc> <loc>https://app.uniswap.org/pools-sitemap.xml</loc>
</sitemap> </sitemap>
<sitemap>
<loc>https://app.uniswap.org/nfts-sitemap.xml</loc>
</sitemap>
</sitemapindex> </sitemapindex>
This diff is collapsed.
...@@ -35,9 +35,8 @@ function AssetActivityProviderInternal({ children }: PropsWithChildren) { ...@@ -35,9 +35,8 @@ function AssetActivityProviderInternal({ children }: PropsWithChildren) {
chains: gqlChains, chains: gqlChains,
// Backend will return off-chain activities even if gqlChains are all testnets. // Backend will return off-chain activities even if gqlChains are all testnets.
includeOffChain: !isTestnetModeEnabled, includeOffChain: !isTestnetModeEnabled,
// Include the externalsessionIDs of all FOR transactions in the local store, // Include the externalsessionIDs of all fiat on-ramp transactions in the local store,
// so that the backend can find the transactions without signature authentication. // so that the backend can find the transactions without signature authentication.
// Note: No FOR transactions are included in activity without explicity passing IDs from local storage
onRampTransactionIDs: transactionIds, onRampTransactionIDs: transactionIds,
}), }),
[account.address, gqlChains, isTestnetModeEnabled, transactionIds], [account.address, gqlChains, isTestnetModeEnabled, transactionIds],
......
...@@ -22,7 +22,6 @@ import { useIsUniExtensionConnected } from 'hooks/useIsUniExtensionConnected' ...@@ -22,7 +22,6 @@ import { useIsUniExtensionConnected } from 'hooks/useIsUniExtensionConnected'
import { useModalState } from 'hooks/useModalState' import { useModalState } from 'hooks/useModalState'
import { useSignOutWithPasskey } from 'hooks/useSignOutWithPasskey' import { useSignOutWithPasskey } from 'hooks/useSignOutWithPasskey'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { SendFormModal } from 'pages/Swap/Send/SendFormModal'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
...@@ -31,7 +30,7 @@ import { useUserHasAvailableClaim, useUserUnclaimedAmount } from 'state/claim/ho ...@@ -31,7 +30,7 @@ import { useUserHasAvailableClaim, useUserUnclaimedAmount } from 'state/claim/ho
import { CopyHelper } from 'theme/components/CopyHelper' import { CopyHelper } from 'theme/components/CopyHelper'
import { Button, Flex, Text } from 'ui/src' import { Button, Flex, Text } from 'ui/src'
import { ArrowDownCircleFilled } from 'ui/src/components/icons/ArrowDownCircleFilled' import { ArrowDownCircleFilled } from 'ui/src/components/icons/ArrowDownCircleFilled'
import { SendAction } from 'ui/src/components/icons/SendAction' import { Bank } from 'ui/src/components/icons/Bank'
import { Shine } from 'ui/src/loading/Shine' import { Shine } from 'ui/src/loading/Shine'
import AnimatedNumber, { import AnimatedNumber, {
BALANCE_CHANGE_INDICATION_DURATION, BALANCE_CHANGE_INDICATION_DURATION,
...@@ -57,6 +56,7 @@ import { TestID } from 'uniswap/src/test/fixtures/testIDs' ...@@ -57,6 +56,7 @@ import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { shortenAddress } from 'utilities/src/addresses' import { shortenAddress } from 'utilities/src/addresses'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { useEvent } from 'utilities/src/react/hooks' import { useEvent } from 'utilities/src/react/hooks'
import { isPathBlocked } from 'utils/blockedPaths'
export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) { export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) {
const { disconnect } = useDisconnect() const { disconnect } = useDisconnect()
...@@ -66,6 +66,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -66,6 +66,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const [modalState, setModalState] = useAtom(miniPortfolioModalStateAtom) const [modalState, setModalState] = useAtom(miniPortfolioModalStateAtom)
const shouldShowBuyFiatButton = !isPathBlocked('/buy')
const isUniExtensionConnected = useIsUniExtensionConnected() const isUniExtensionConnected = useIsUniExtensionConnected()
const { isTestnetModeEnabled } = useEnabledChains() const { isTestnetModeEnabled } = useEnabledChains()
const connectedAccount = useAccount() const connectedAccount = useAccount()
...@@ -98,11 +99,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -98,11 +99,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const openAddressQRModal = useEvent(() => setModalState(ModalState.QR_CODE)) const openAddressQRModal = useEvent(() => setModalState(ModalState.QR_CODE))
const openCEXTransferModal = useEvent(() => setModalState(ModalState.CEX_TRANSFER)) const openCEXTransferModal = useEvent(() => setModalState(ModalState.CEX_TRANSFER))
const openReceiveCryptoModal = useEvent(() => setModalState(ModalState.DEFAULT)) const openReceiveCryptoModal = useEvent(() => setModalState(ModalState.DEFAULT))
const {
isOpen: isSendFormModalOpen,
openModal: openSendFormModal,
closeModal: closeSendFormModal,
} = useModalState(ModalName.Send)
const { data, networkStatus, loading } = usePortfolioTotalValue({ const { data, networkStatus, loading } = usePortfolioTotalValue({
address: account, address: account,
...@@ -218,12 +214,14 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -218,12 +214,14 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
) : ( ) : (
<> <>
<Flex row gap="$gap8"> <Flex row gap="$gap8">
<ActionTile {shouldShowBuyFiatButton && (
dataTestId={TestID.Send} <ActionTile
Icon={<SendAction size={24} color="$accent1" />} dataTestId={TestID.WalletBuyCrypto}
name={t('common.send.button')} Icon={<Bank size={24} color="$accent1" />}
onClick={openSendFormModal} name={t('common.buy.label')}
/> onClick={handleBuyCryptoClick}
/>
)}
<ActionTile <ActionTile
dataTestId={TestID.WalletReceiveCrypto} dataTestId={TestID.WalletReceiveCrypto}
Icon={<ArrowDownCircleFilled size={24} color="$accent1" />} Icon={<ArrowDownCircleFilled size={24} color="$accent1" />}
...@@ -250,7 +248,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account ...@@ -250,7 +248,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
</Flex> </Flex>
</Flex> </Flex>
{modalState !== undefined && <ReceiveCryptoModal />} {modalState !== undefined && <ReceiveCryptoModal />}
{isSendFormModalOpen && <SendFormModal isModalOpen={isSendFormModalOpen} onClose={closeSendFormModal} />}
{displayDelegationMismatchModal && ( {displayDelegationMismatchModal && (
<DelegationMismatchModal onClose={() => setDisplayDelegationMismatchModal(false)} /> <DelegationMismatchModal onClose={() => setDisplayDelegationMismatchModal(false)} />
)} )}
......
...@@ -140,7 +140,7 @@ export function useCancelOrdersGasEstimate(orders?: UniswapXOrderDetails[]): Gas ...@@ -140,7 +140,7 @@ export function useCancelOrdersGasEstimate(orders?: UniswapXOrderDetails[]): Gas
: undefined, : undefined,
[orders], [orders],
) )
const cancelTransaction = useCreateCancelTransactionRequest(cancelTransactionParams) ?? undefined const cancelTransaction = useCreateCancelTransactionRequest(cancelTransactionParams)
const gasEstimate = useTransactionGasFee(cancelTransaction, GasSpeed.Fast) const gasEstimate = useTransactionGasFee(cancelTransaction, GasSpeed.Fast)
return gasEstimate return gasEstimate
} }
...@@ -12,11 +12,6 @@ import { ...@@ -12,11 +12,6 @@ import {
OrderTextTable, OrderTextTable,
getActivityTitle, getActivityTitle,
} from 'components/AccountDrawer/MiniPortfolio/constants' } from 'components/AccountDrawer/MiniPortfolio/constants'
import { FiatOnRampTransactionStatus } from 'state/fiatOnRampTransactions/types'
import {
forTransactionStatusToTransactionStatus,
statusToTransactionInfoStatus,
} from 'state/fiatOnRampTransactions/utils'
import { isOnChainOrder, useAllSignatures } from 'state/signatures/hooks' import { isOnChainOrder, useAllSignatures } from 'state/signatures/hooks'
import { SignatureDetails, SignatureType } from 'state/signatures/types' import { SignatureDetails, SignatureType } from 'state/signatures/types'
import { useMultichainTransactions } from 'state/transactions/hooks' import { useMultichainTransactions } from 'state/transactions/hooks'
...@@ -44,7 +39,6 @@ import { nativeOnChain } from 'uniswap/src/constants/tokens' ...@@ -44,7 +39,6 @@ import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FORTransaction } from 'uniswap/src/features/fiatOnRamp/types'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import i18n from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
import { isAddress } from 'utilities/src/addresses' import { isAddress } from 'utilities/src/addresses'
...@@ -53,7 +47,6 @@ import { logger } from 'utilities/src/logger/logger' ...@@ -53,7 +47,6 @@ import { logger } from 'utilities/src/logger/logger'
import { ReactQueryCacheKey } from 'utilities/src/reactQuery/cache' import { ReactQueryCacheKey } from 'utilities/src/reactQuery/cache'
type FormatNumberFunctionType = ReturnType<typeof useLocalizationContext>['formatNumberOrString'] type FormatNumberFunctionType = ReturnType<typeof useLocalizationContext>['formatNumberOrString']
type FormatFiatPriceFunctionType = ReturnType<typeof useLocalizationContext>['convertFiatAmountFormatted']
function buildCurrencyDescriptor( function buildCurrencyDescriptor(
currencyA: Currency | undefined, currencyA: Currency | undefined,
...@@ -392,58 +385,6 @@ export function getSignatureToActivityQueryOptions( ...@@ -392,58 +385,6 @@ export function getSignatureToActivityQueryOptions(
}) })
} }
export function getFORTransactionToActivityQueryOptions(
transaction: FORTransaction | undefined,
formatNumber: FormatNumberFunctionType,
formatFiatPrice: FormatFiatPriceFunctionType,
) {
return queryOptions({
queryKey: [ReactQueryCacheKey.TransactionToActivity, transaction],
queryFn: async () => forTransactionToActivity(transaction, formatNumber, formatFiatPrice),
})
}
const forTransactionToActivity = async (
transaction: FORTransaction | undefined,
formatNumber: FormatNumberFunctionType,
formatFiatPrice: FormatFiatPriceFunctionType,
) => {
if (!transaction) {
return undefined
}
const chainId = Number(transaction.cryptoDetails.chainId) as UniverseChainId
const currency = await getCurrency(transaction.sourceCurrencyCode, chainId)
const status = statusToTransactionInfoStatus(transaction.status)
const serviceProvider = transaction.serviceProviderDetails.name
const tokenAmount = formatNumber({ value: transaction.sourceAmount, type: NumberType.TokenNonTx })
const fiatAmount = formatFiatPrice(transaction.destinationAmount, NumberType.FiatTokenPrice)
let title = ''
switch (status) {
case FiatOnRampTransactionStatus.PENDING:
title = i18n.t('transaction.status.sale.pendingOn', { serviceProvider })
break
case FiatOnRampTransactionStatus.COMPLETE:
title = i18n.t('transaction.status.sale.successOn', { serviceProvider })
break
case FiatOnRampTransactionStatus.FAILED:
title = i18n.t('transaction.status.sale.failedOn', { serviceProvider })
break
}
return {
hash: transaction.externalSessionId,
chainId,
title,
descriptor: `${tokenAmount} ${transaction?.sourceCurrencyCode} ${i18n.t('common.for').toLocaleLowerCase()} ${fiatAmount}`,
currencies: [currency],
status: forTransactionStatusToTransactionStatus(status),
timestamp: convertToSecTimestamp(Number(transaction.createdAt)),
from: transaction.cryptoDetails.walletAddress,
}
}
function convertToSecTimestamp(timestamp: number) { function convertToSecTimestamp(timestamp: number) {
// UNIX timestamp in ms for Jan 1, 2100 // UNIX timestamp in ms for Jan 1, 2100
const threshold: number = 4102444800000 const threshold: number = 4102444800000
......
...@@ -34,7 +34,6 @@ import { ...@@ -34,7 +34,6 @@ import {
NftApprovalPartsFragment, NftApprovalPartsFragment,
NftApproveForAllPartsFragment, NftApproveForAllPartsFragment,
NftTransferPartsFragment, NftTransferPartsFragment,
OffRampTransactionDetailsPartsFragment,
OnRampTransactionDetailsPartsFragment, OnRampTransactionDetailsPartsFragment,
OnRampTransferPartsFragment, OnRampTransferPartsFragment,
TokenApprovalPartsFragment, TokenApprovalPartsFragment,
...@@ -486,7 +485,6 @@ function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: For ...@@ -486,7 +485,6 @@ function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: For
type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment } type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment }
type FiatOnRampActivity = AssetActivityPartsFragment & { details: OnRampTransactionDetailsPartsFragment } type FiatOnRampActivity = AssetActivityPartsFragment & { details: OnRampTransactionDetailsPartsFragment }
type FiatOffRampActivity = AssetActivityPartsFragment & { details: OffRampTransactionDetailsPartsFragment }
function parseSendReceive( function parseSendReceive(
changes: TransactionChanges, changes: TransactionChanges,
...@@ -738,42 +736,6 @@ function parseFiatOnRampTransaction(activity: TransactionActivity | FiatOnRampAc ...@@ -738,42 +736,6 @@ function parseFiatOnRampTransaction(activity: TransactionActivity | FiatOnRampAc
} }
} }
function parseFiatOffRampTransaction(activity: FiatOffRampActivity): Activity {
const chainId = supportedChainIdFromGQLChain(activity.chain)
if (!chainId) {
const error = new Error('Invalid activity from unsupported chain received from GQL')
logger.error(error, {
tags: {
file: 'parseRemote',
function: 'parseRemote',
},
extra: { activity },
})
throw error
}
const { offRampTransfer } = activity.details
return {
from: activity.details.senderAddress,
hash: activity.id,
chainId,
timestamp: activity.timestamp,
logos: [offRampTransfer.token.project?.logoUrl],
currencies: [gqlToCurrency(offRampTransfer.token)],
title: i18n.t('transaction.status.sale.successOn', {
serviceProvider: offRampTransfer.serviceProvider.name,
}),
descriptor: i18n.t('fiatOffRamp.exchangeRate', {
inputAmount: offRampTransfer.amount,
inputSymbol: offRampTransfer.token.symbol,
outputAmount: offRampTransfer.destinationAmount,
outputSymbol: offRampTransfer.destinationCurrency,
}),
suffixIconSrc: offRampTransfer.serviceProvider.logoDarkUrl,
status: activity.details.status,
}
}
function parseRemoteActivity( function parseRemoteActivity(
assetActivity: AssetActivityPartsFragment | undefined, assetActivity: AssetActivityPartsFragment | undefined,
account: string, account: string,
...@@ -784,8 +746,13 @@ function parseRemoteActivity( ...@@ -784,8 +746,13 @@ function parseRemoteActivity(
return undefined return undefined
} }
if (assetActivity.details.__typename === 'OffRampTransactionDetails') { // TODO: skip until offramp transactions are supported
return parseFiatOffRampTransaction(assetActivity as FiatOffRampActivity) if (
assetActivity.details.__typename === 'OffRampTransactionDetails' ||
(assetActivity.details.__typename === 'TransactionDetails' &&
assetActivity.details.type === TransactionType.OffRamp)
) {
return undefined
} }
if (assetActivity.details.__typename === 'SwapOrderDetails') { if (assetActivity.details.__typename === 'SwapOrderDetails') {
......
import { TransactionRequest } from '@ethersproject/abstract-provider' import { TransactionRequest } from '@ethersproject/abstract-provider'
import { Web3Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import { useQuery } from '@tanstack/react-query'
import { permit2Address } from '@uniswap/permit2-sdk' import { permit2Address } from '@uniswap/permit2-sdk'
import { import {
CosignedPriorityOrder, CosignedPriorityOrder,
...@@ -30,8 +29,7 @@ import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' ...@@ -30,8 +29,7 @@ import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import i18n from 'uniswap/src/i18n' import i18n from 'uniswap/src/i18n'
import { getContract } from 'utilities/src/contracts/getContract' import { getContract } from 'utilities/src/contracts/getContract'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ReactQueryCacheKey } from 'utilities/src/reactQuery/cache' import { useAsyncData } from 'utilities/src/react/hooks'
import { queryWithoutCache } from 'utilities/src/reactQuery/queryOptions'
import { WrongChainError } from 'utils/errors' import { WrongChainError } from 'utils/errors'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage' import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
...@@ -230,7 +228,7 @@ export function useCreateCancelTransactionRequest( ...@@ -230,7 +228,7 @@ export function useCreateCancelTransactionRequest(
chainId: UniverseChainId chainId: UniverseChainId
} }
| undefined, | undefined,
): Maybe<TransactionRequest> { ): TransactionRequest | undefined {
const permit2 = useContract<Permit2>(permit2Address(params?.chainId), PERMIT2_ABI, true) const permit2 = useContract<Permit2>(permit2Address(params?.chainId), PERMIT2_ABI, true)
const transactionFetcher = useCallback(() => { const transactionFetcher = useCallback(() => {
if ( if (
...@@ -239,17 +237,12 @@ export function useCreateCancelTransactionRequest( ...@@ -239,17 +237,12 @@ export function useCreateCancelTransactionRequest(
params.orders.filter(({ encodedOrder }) => Boolean(encodedOrder)).length === 0 || params.orders.filter(({ encodedOrder }) => Boolean(encodedOrder)).length === 0 ||
!permit2 !permit2
) { ) {
return null return undefined
} }
return getCancelMultipleUniswapXOrdersTransaction(params.orders, params.chainId, permit2) return getCancelMultipleUniswapXOrdersTransaction(params.orders, params.chainId, permit2)
}, [params, permit2]) }, [params, permit2])
return useQuery( return useAsyncData(transactionFetcher).data
queryWithoutCache({
queryKey: [ReactQueryCacheKey.CancelTransactionRequest, params],
queryFn: transactionFetcher,
}),
).data
} }
export function isLimitCancellable(order: UniswapXOrderDetails) { export function isLimitCancellable(order: UniswapXOrderDetails) {
......
...@@ -92,7 +92,7 @@ test.describe('Mini Portfolio account drawer', () => { ...@@ -92,7 +92,7 @@ test.describe('Mini Portfolio account drawer', () => {
// Verify wallet state // Verify wallet state
await expect(page.getByTestId(TestID.MiniPortfolioNavbar)).toContainText('Tokens') await expect(page.getByTestId(TestID.MiniPortfolioNavbar)).toContainText('Tokens')
await expect(page.getByTestId(TestID.MiniPortfolioPage)).toContainText('Hidden (4)') await expect(page.getByTestId(TestID.MiniPortfolioPage)).toContainText('Hidden (5)')
// Check NFTs section // Check NFTs section
await page.getByTestId(TestID.MiniPortfolioNavbar).getByText('NFTs').click() await page.getByTestId(TestID.MiniPortfolioNavbar).getByText('NFTs').click()
......
import { OutageCloseButton } from 'components/Banner/Outage/OutageBanner'
import { useTheme } from 'lib/styled-components'
import { useState } from 'react'
import { Globe } from 'react-feather'
import { useTranslation } from 'react-i18next'
import { Flex, Text } from 'ui/src'
import { zIndexes } from 'ui/src/theme'
export function MonadOutageBanner() {
const { t } = useTranslation()
const [hidden, setHidden] = useState(false)
const theme = useTheme()
if (hidden) {
return null
}
return (
<Flex
width={360}
maxWidth="95%"
$platform-web={{ position: 'fixed' }}
bottom={40}
right={20}
backgroundColor={theme.surface2}
zIndex={zIndexes.sticky}
borderRadius="$rounded20"
borderStyle="solid"
borderWidth={1.3}
borderColor={theme.surface3}
$lg={{
bottom: 62,
}}
$sm={{
bottom: 80,
}}
$xs={{
right: 10,
left: 10,
}}
>
<Flex row p="$spacing8" borderRadius="$rounded20" height="100%">
<Flex
centered
m="$spacing12"
mr="spacing6"
height={45}
width={45}
backgroundColor={theme.warning2}
borderRadius="$rounded12"
>
<Globe size={28} color={theme.warning2} />
</Flex>
<Flex gap="$spacing2" p={10} $xs={{ maxWidth: 270 }} flexShrink={1}>
<Text variant="body2" color={theme.neutral1}>
{t('home.banner.testnetMode.outage.monad.title')}
</Text>
<Text variant="body3" color={theme.neutral2}>
{t('home.banner.testnetMode.outage.monad.description')}
</Text>
</Flex>
<OutageCloseButton
data-testid="monad-outage-banner"
onClick={() => {
setHidden(true)
}}
/>
</Flex>
</Flex>
)
}
...@@ -18,7 +18,7 @@ export function getOutageBannerSessionStorageKey(chainId: UniverseChainId) { ...@@ -18,7 +18,7 @@ export function getOutageBannerSessionStorageKey(chainId: UniverseChainId) {
} }
// TODO replace with IconButton when it's available from buttons migration // TODO replace with IconButton when it's available from buttons migration
const OutageCloseButton = tamaguiStyled(X, { export const OutageCloseButton = tamaguiStyled(X, {
...ClickableTamaguiStyle, ...ClickableTamaguiStyle,
size: iconSizes.icon24, size: iconSizes.icon24,
p: '$spacing4', p: '$spacing4',
......
import { InterfacePageName } from '@uniswap/analytics-events' import { InterfacePageName } from '@uniswap/analytics-events'
import { MonadOutageBanner } from 'components/Banner/Outage/MonadOutageBanner'
import { OutageBanner, getOutageBannerSessionStorageKey } from 'components/Banner/Outage/OutageBanner' import { OutageBanner, getOutageBannerSessionStorageKey } from 'components/Banner/Outage/OutageBanner'
import { LPIncentiveAnnouncementBanner } from 'components/Liquidity/LPIncentiveAnnouncementBanner' import { LPIncentiveAnnouncementBanner } from 'components/Liquidity/LPIncentiveAnnouncementBanner'
import { manualChainOutageAtom } from 'featureFlags/flags/outageBanner' import { manualChainOutageAtom } from 'featureFlags/flags/outageBanner'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
...@@ -18,6 +20,9 @@ export function Banners() { ...@@ -18,6 +20,9 @@ export function Banners() {
const manualOutage = useAtomValue(manualChainOutageAtom) const manualOutage = useAtomValue(manualChainOutageAtom)
const isMonadDownFlag = useFeatureFlag(FeatureFlags.MonadTestnetDown)
const { isTestnetModeEnabled } = useEnabledChains()
// Calculate the chainId for the current page's contextual chain (e.g. /tokens/ethereum or /tokens/arbitrum), if it exists. // Calculate the chainId for the current page's contextual chain (e.g. /tokens/ethereum or /tokens/arbitrum), if it exists.
const pageChainId = useMemo(() => { const pageChainId = useMemo(() => {
const chainUrlParam = pathname.split('/').find(isChainUrlParam) const chainUrlParam = pathname.split('/').find(isChainUrlParam)
...@@ -40,6 +45,11 @@ export function Banners() { ...@@ -40,6 +45,11 @@ export function Banners() {
) )
}, [currentPage, currentPageHasManualOutage, pageChainId]) }, [currentPage, currentPageHasManualOutage, pageChainId])
// Monad Outage Banner takes precedence if in testnet mode
if (isMonadDownFlag && isTestnetModeEnabled) {
return <MonadOutageBanner />
}
// Outage Banners should take precedence over other promotional banners // Outage Banners should take precedence over other promotional banners
if (pageChainId && showOutageBanner) { if (pageChainId && showOutageBanner) {
return ( return (
......
...@@ -15,6 +15,8 @@ import { useUniswapContext } from 'uniswap/src/contexts/UniswapContext' ...@@ -15,6 +15,8 @@ import { useUniswapContext } from 'uniswap/src/contexts/UniswapContext'
import { DEFAULT_MS_BEFORE_WARNING, getChainInfo } from 'uniswap/src/features/chains/chainInfo' import { DEFAULT_MS_BEFORE_WARNING, getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { AVERAGE_L1_BLOCK_TIME_MS } from 'uniswap/src/features/transactions/hooks/usePollingIntervalByChain' import { AVERAGE_L1_BLOCK_TIME_MS } from 'uniswap/src/features/transactions/hooks/usePollingIntervalByChain'
const BodyRow = styled.div` const BodyRow = styled.div`
...@@ -59,6 +61,7 @@ const CloseButton = tamaguiStyled(X, { ...@@ -59,6 +61,7 @@ const CloseButton = tamaguiStyled(X, {
export function ChainConnectivityWarning() { export function ChainConnectivityWarning() {
const { defaultChainId } = useEnabledChains() const { defaultChainId } = useEnabledChains()
const [hide, setHide] = useState(false) const [hide, setHide] = useState(false)
const isMonadDownFlag = useFeatureFlag(FeatureFlags.MonadTestnetDown)
const { swapInputChainId: chainId } = useUniswapContext() const { swapInputChainId: chainId } = useUniswapContext()
const info = getChainInfo(chainId ?? defaultChainId) const info = getChainInfo(chainId ?? defaultChainId)
const label = info.label const label = info.label
...@@ -73,8 +76,9 @@ export function ChainConnectivityWarning() { ...@@ -73,8 +76,9 @@ export function ChainConnectivityWarning() {
const blockTime = useCurrentBlockTimestamp({ refetchInterval: ms('5min') }) const blockTime = useCurrentBlockTimestamp({ refetchInterval: ms('5min') })
const warning = Boolean(!!blockTime && machineTime - Number(blockTime) * 1000 > waitMsBeforeWarning) const warning = Boolean(!!blockTime && machineTime - Number(blockTime) * 1000 > waitMsBeforeWarning)
const isMonadDown = chainId === UniverseChainId.MonadTestnet && isMonadDownFlag
if (hide || !warning || isLandingPage) { if (hide || (!isMonadDown && (!warning || isLandingPage))) {
return null return null
} }
......
import { Currency, Percent } from '@uniswap/sdk-core' import { Currency, Percent } from '@uniswap/sdk-core'
import { LiquidityBarData } from 'components/Charts/LiquidityChart/types'
import { ChartEntry } from 'components/Charts/LiquidityRangeInput/types' import { ChartEntry } from 'components/Charts/LiquidityRangeInput/types'
import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { Flex, FlexProps, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { useUSDCValue } from 'uniswap/src/features/transactions/hooks/useUSDCPrice' import { useUSDCValue } from 'uniswap/src/features/transactions/hooks/useUSDCPrice'
...@@ -21,7 +20,7 @@ export function TickTooltip({ ...@@ -21,7 +20,7 @@ export function TickTooltip({
baseCurrency, baseCurrency,
}: { }: {
hoverY: number hoverY: number
hoveredTick: ChartEntry | LiquidityBarData hoveredTick: ChartEntry
currentPrice: number currentPrice: number
currentTick?: number currentTick?: number
containerHeight: number containerHeight: number
...@@ -30,41 +29,8 @@ export function TickTooltip({ ...@@ -30,41 +29,8 @@ export function TickTooltip({
quoteCurrency: Currency quoteCurrency: Currency
baseCurrency: Currency baseCurrency: Currency
}) { }) {
const atTop = hoverY < 20
const atBottom = containerHeight - hoverY < 20
return (
<TickTooltipContent
position="absolute"
top={hoverY - 18}
right={contentWidth + axisLabelPaneWidth + 8}
transform={atBottom ? 'translateY(-12px)' : atTop ? 'translateY(14px)' : undefined}
currentPrice={currentPrice}
hoveredTick={hoveredTick}
currentTick={currentTick}
quoteCurrency={quoteCurrency}
baseCurrency={baseCurrency}
/>
)
}
export function TickTooltipContent({
currentPrice,
hoveredTick,
currentTick,
quoteCurrency,
baseCurrency,
showQuoteCurrencyFirst = true,
...props
}: {
currentPrice: number
hoveredTick: ChartEntry | LiquidityBarData
currentTick?: number
quoteCurrency: Currency
baseCurrency: Currency
showQuoteCurrencyFirst?: boolean
} & FlexProps) {
const { formatPercent, convertFiatAmountFormatted } = useLocalizationContext() const { formatPercent, convertFiatAmountFormatted } = useLocalizationContext()
const amountBaseLockedUSD = useUSDCValue(tryParseCurrencyAmount(hoveredTick.amount1Locked?.toFixed(2), baseCurrency)) const amountBaseLockedUSD = useUSDCValue(tryParseCurrencyAmount(hoveredTick.amount1Locked?.toFixed(2), baseCurrency))
const amountQuoteLockedUSD = useUSDCValue( const amountQuoteLockedUSD = useUSDCValue(
tryParseCurrencyAmount(hoveredTick.amount0Locked?.toFixed(2), quoteCurrency), tryParseCurrencyAmount(hoveredTick.amount0Locked?.toFixed(2), quoteCurrency),
...@@ -74,22 +40,25 @@ export function TickTooltipContent({ ...@@ -74,22 +40,25 @@ export function TickTooltipContent({
return null return null
} }
const price0 = typeof hoveredTick.price0 === 'string' ? parseFloat(hoveredTick.price0) : hoveredTick.price0 const atTop = hoverY < 20
const showQuoteCurrency = showQuoteCurrencyFirst ? currentPrice >= price0 : currentPrice <= price0 const atBottom = containerHeight - hoverY < 20
return ( return (
<Flex <Flex
position="absolute"
p="$padding8" p="$padding8"
gap="$gap4" gap="$gap4"
top={hoverY - 18}
minWidth={150} minWidth={150}
right={contentWidth + axisLabelPaneWidth + 8}
borderRadius="$rounded12" borderRadius="$rounded12"
borderColor="$surface3" borderColor="$surface3"
borderWidth="$spacing1" borderWidth="$spacing1"
backgroundColor="$surface2" backgroundColor="$surface2"
pointerEvents="none" pointerEvents="none"
{...props} transform={atBottom ? 'translateY(-12px)' : atTop ? 'translateY(14px)' : undefined}
> >
{(showQuoteCurrency || hoveredTick.tick === currentTick) && ( {(currentPrice >= hoveredTick.price0 || hoveredTick.tick === currentTick) && (
<Flex justifyContent="space-between" row alignItems="center" gap="$gap8"> <Flex justifyContent="space-between" row alignItems="center" gap="$gap8">
<Flex row gap="$gap4" alignItems="center"> <Flex row gap="$gap4" alignItems="center">
<DoubleCurrencyLogo currencies={[quoteCurrency]} size={iconSizes.icon16} /> <DoubleCurrencyLogo currencies={[quoteCurrency]} size={iconSizes.icon16} />
...@@ -114,7 +83,7 @@ export function TickTooltipContent({ ...@@ -114,7 +83,7 @@ export function TickTooltipContent({
</Flex> </Flex>
</Flex> </Flex>
)} )}
{(!showQuoteCurrency || hoveredTick.tick === currentTick) && ( {(currentPrice <= hoveredTick.price0 || hoveredTick.tick === currentTick) && (
<Flex justifyContent="space-between" row alignItems="center" gap="$gap8"> <Flex justifyContent="space-between" row alignItems="center" gap="$gap8">
<Flex row gap="$gap4" alignItems="center"> <Flex row gap="$gap4" alignItems="center">
<DoubleCurrencyLogo currencies={[baseCurrency]} size={iconSizes.icon16} /> <DoubleCurrencyLogo currencies={[baseCurrency]} size={iconSizes.icon16} />
......
...@@ -43,7 +43,6 @@ interface ChartDataParams<TDataType extends SeriesDataItemType> { ...@@ -43,7 +43,6 @@ interface ChartDataParams<TDataType extends SeriesDataItemType> {
data: TDataType[] data: TDataType[]
/** Repesents whether `data` is stale. If true, stale UI will appear */ /** Repesents whether `data` is stale. If true, stale UI will appear */
stale?: boolean stale?: boolean
hideTooltipBorder?: boolean
} }
export type ChartModelParams<TDataType extends SeriesDataItemType> = ChartUtilParams<TDataType> & export type ChartModelParams<TDataType extends SeriesDataItemType> = ChartUtilParams<TDataType> &
...@@ -329,11 +328,10 @@ export function Chart<TParamType extends ChartDataParams<TDataType>, TDataType e ...@@ -329,11 +328,10 @@ export function Chart<TParamType extends ChartDataParams<TDataType>, TDataType e
> >
{children && children(crosshairData)} {children && children(crosshairData)}
{TooltipBody && crosshairData && ( {TooltipBody && crosshairData && (
<ChartTooltip id={chartModelRef.current?.tooltipId} includeBorder={!params.hideTooltipBorder}> <ChartTooltip id={chartModelRef.current?.tooltipId}>
<TooltipBody data={crosshairData} /> <TooltipBody data={crosshairData} />
</ChartTooltip> </ChartTooltip>
)} )}
{params.stale && <StaleBanner />} {params.stale && <StaleBanner />}
</Flex> </Flex>
) )
...@@ -345,20 +343,13 @@ const ChartTooltip = styled(Flex, { ...@@ -345,20 +343,13 @@ const ChartTooltip = styled(Flex, {
left: 0, left: 0,
top: 0, top: 0,
zIndex: '$tooltip', zIndex: '$tooltip',
borderWidth: 0, backgroundColor: '$surface5',
backdropFilter: 'blur(8px)',
borderRadius: '$rounded8',
borderColor: '$surface3',
borderStyle: 'solid', borderStyle: 'solid',
variants: { borderWidth: 1,
includeBorder: { p: '$spacing8',
true: {
backgroundColor: '$surface5',
backdropFilter: 'blur(8px)',
borderRadius: '$rounded8',
borderColor: '$surface3',
borderWidth: 1,
p: '$spacing8',
},
},
},
}) })
const StaleBannerWrapper = styled(ChartTooltip, { const StaleBannerWrapper = styled(ChartTooltip, {
......
...@@ -5,7 +5,11 @@ import { FeeAmount, Pool as PoolV3, TICK_SPACINGS, TickMath as TickMathV3, tickT ...@@ -5,7 +5,11 @@ import { FeeAmount, Pool as PoolV3, TICK_SPACINGS, TickMath as TickMathV3, tickT
import { Pool as PoolV4, tickToPrice as tickToPriceV4 } from '@uniswap/v4-sdk' import { Pool as PoolV4, tickToPrice as tickToPriceV4 } from '@uniswap/v4-sdk'
import { ChartHoverData, ChartModel, ChartModelParams } from 'components/Charts/ChartModel' import { ChartHoverData, ChartModel, ChartModelParams } from 'components/Charts/ChartModel'
import { LiquidityBarSeries } from 'components/Charts/LiquidityChart/liquidity-bar-series' import { LiquidityBarSeries } from 'components/Charts/LiquidityChart/liquidity-bar-series'
import { LiquidityBarData, LiquidityBarProps, LiquidityBarSeriesOptions } from 'components/Charts/LiquidityChart/types' import {
LiquidityBarData,
LiquidityBarProps,
LiquidityBarSeriesOptions,
} from 'components/Charts/LiquidityChart/renderer'
import { ZERO_ADDRESS } from 'constants/misc' import { ZERO_ADDRESS } from 'constants/misc'
import { usePoolActiveLiquidity } from 'hooks/usePoolTickData' import { usePoolActiveLiquidity } from 'hooks/usePoolTickData'
import JSBI from 'jsbi' import JSBI from 'jsbi'
......
import { LiquidityBarSeriesRenderer } from 'components/Charts/LiquidityChart/renderer' import {
import { LiquidityBarData, LiquidityBarProps, LiquidityBarSeriesOptions } from 'components/Charts/LiquidityChart/types' LiquidityBarData,
LiquidityBarProps,
LiquidityBarSeriesOptions,
LiquidityBarSeriesRenderer,
} from 'components/Charts/LiquidityChart/renderer'
import { import {
CustomSeriesPricePlotValues, CustomSeriesPricePlotValues,
ICustomSeriesPaneView, ICustomSeriesPaneView,
......
import { LiquidityBarData, LiquidityBarProps, LiquidityBarSeriesOptions } from 'components/Charts/LiquidityChart/types'
import { ColumnPosition, calculateColumnPositionsInPlace, positionsBox } from 'components/Charts/VolumeChart/utils' import { ColumnPosition, calculateColumnPositionsInPlace, positionsBox } from 'components/Charts/VolumeChart/utils'
import { roundRect } from 'components/Charts/utils' import { roundRect } from 'components/Charts/utils'
import { BitmapCoordinatesRenderingScope, CanvasRenderingTarget2D } from 'fancy-canvas' import { BitmapCoordinatesRenderingScope, CanvasRenderingTarget2D } from 'fancy-canvas'
import { ICustomSeriesPaneRenderer, PaneRendererCustomData, PriceToCoordinateConverter, Time } from 'lightweight-charts' import {
CustomData,
CustomSeriesOptions,
ICustomSeriesPaneRenderer,
PaneRendererCustomData,
PriceToCoordinateConverter,
Time,
UTCTimestamp,
} from 'lightweight-charts'
export interface LiquidityBarData extends CustomData {
time: UTCTimestamp
tick: number
price0: string
price1: string
liquidity: number
amount0Locked: number
amount1Locked: number
}
interface LiquidityBarItem { interface LiquidityBarItem {
x: number x: number
...@@ -11,6 +28,18 @@ interface LiquidityBarItem { ...@@ -11,6 +28,18 @@ interface LiquidityBarItem {
tick: number tick: number
} }
export interface LiquidityBarProps {
tokenAColor: string
tokenBColor: string
highlightColor: string
activeTick?: number
activeTickProgress?: number
}
export interface LiquidityBarSeriesOptions extends CustomSeriesOptions, LiquidityBarProps {
hoveredTick?: number
}
export class LiquidityBarSeriesRenderer<TData extends LiquidityBarData> implements ICustomSeriesPaneRenderer { export class LiquidityBarSeriesRenderer<TData extends LiquidityBarData> implements ICustomSeriesPaneRenderer {
_data: PaneRendererCustomData<Time, TData> | null = null _data: PaneRendererCustomData<Time, TData> | null = null
_options: LiquidityBarProps & Partial<LiquidityBarSeriesOptions> _options: LiquidityBarProps & Partial<LiquidityBarSeriesOptions>
......
import { CustomData, CustomSeriesOptions, UTCTimestamp } from 'lightweight-charts'
export interface LiquidityBarData extends CustomData {
time: UTCTimestamp
tick: number
price0: string
price1: string
liquidity: number
amount0Locked: number
amount1Locked: number
}
export interface LiquidityBarProps {
tokenAColor: string
tokenBColor: string
highlightColor: string
activeTick?: number
activeTickProgress?: number
}
export interface LiquidityBarSeriesOptions extends CustomSeriesOptions, LiquidityBarProps {
hoveredTick?: number
}
...@@ -208,8 +208,8 @@ export function PriceChartDelta({ startingPrice, endingPrice, noColor }: PriceCh ...@@ -208,8 +208,8 @@ export function PriceChartDelta({ startingPrice, endingPrice, noColor }: PriceCh
return ( return (
<Text variant="body2" display="flex" alignItems="center" gap="$gap4"> <Text variant="body2" display="flex" alignItems="center" gap="$gap4">
{delta && <DeltaArrow delta={delta} formattedDelta={formatPercent(Math.abs(delta))} noColor={noColor} />} <DeltaArrow delta={delta} formattedDelta={formatPercent(Math.abs(delta))} noColor={noColor} />
<DeltaText delta={delta}>{delta ? formatPercent(Math.abs(delta)) : '-'}</DeltaText> <DeltaText delta={delta}>{formatPercent(Math.abs(delta))}</DeltaText>
</Text> </Text>
) )
} }
......
...@@ -229,7 +229,9 @@ export default function FeatureFlagModal() { ...@@ -229,7 +229,9 @@ export default function FeatureFlagModal() {
/> />
</FeatureFlagGroup> </FeatureFlagGroup>
<FeatureFlagGroup name="New Chains"> <FeatureFlagGroup name="New Chains">
<FeatureFlagOption flag={FeatureFlags.MonadTestnet} label="Enable Monad Testnet" />
<FeatureFlagOption flag={FeatureFlags.Soneium} label="Enable Soneium" /> <FeatureFlagOption flag={FeatureFlags.Soneium} label="Enable Soneium" />
<FeatureFlagOption flag={FeatureFlags.MonadTestnetDown} label="Enable Monad Testnet Down Banner" />
</FeatureFlagGroup> </FeatureFlagGroup>
<FeatureFlagGroup name="Network Requests"> <FeatureFlagGroup name="Network Requests">
<DynamicConfigDropdown <DynamicConfigDropdown
......
import { CreditCardIcon } from 'components/Icons/CreditCard' import { CreditCardIcon } from 'components/Icons/CreditCard'
import { Limit } from 'components/Icons/Limit' import { Limit } from 'components/Icons/Limit'
import { Send } from 'components/Icons/Send'
import { SwapV2 } from 'components/Icons/SwapV2' import { SwapV2 } from 'components/Icons/SwapV2'
import { MenuItem } from 'components/NavBar/CompanyMenu/Content' import { MenuItem } from 'components/NavBar/CompanyMenu/Content'
import { useTheme } from 'lib/styled-components' import { useTheme } from 'lib/styled-components'
...@@ -45,6 +46,16 @@ export const useTabsContent = (): TabsSection[] => { ...@@ -45,6 +46,16 @@ export const useTabsContent = (): TabsSection[] => {
href: '/limit', href: '/limit',
internal: true, internal: true,
}, },
...(isFiatOffRampEnabled
? []
: [
{
label: t('common.send.button'),
icon: <Send fill={theme.neutral2} />,
href: '/send',
internal: true,
},
]),
{ {
label: t('common.buy.label'), label: t('common.buy.label'),
icon: <CreditCardIcon fill={theme.neutral2} />, icon: <CreditCardIcon fill={theme.neutral2} />,
......
...@@ -2,7 +2,6 @@ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persist ...@@ -2,7 +2,6 @@ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persist
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { type PropsWithChildren } from 'react' import { type PropsWithChildren } from 'react'
import { SharedQueryClient } from 'uniswap/src/data/apiClients/SharedQueryClient' import { SharedQueryClient } from 'uniswap/src/data/apiClients/SharedQueryClient'
import { sharedDehydrateOptions } from 'uniswap/src/data/apiClients/sharedDehydrateOptions'
import { MAX_REACT_QUERY_CACHE_TIME_MS } from 'utilities/src/time/time' import { MAX_REACT_QUERY_CACHE_TIME_MS } from 'utilities/src/time/time'
const persistOptions: React.ComponentProps<typeof PersistQueryClientProvider>['persistOptions'] = { const persistOptions: React.ComponentProps<typeof PersistQueryClientProvider>['persistOptions'] = {
...@@ -10,7 +9,6 @@ const persistOptions: React.ComponentProps<typeof PersistQueryClientProvider>['p ...@@ -10,7 +9,6 @@ const persistOptions: React.ComponentProps<typeof PersistQueryClientProvider>['p
buster: 'v0', buster: 'v0',
maxAge: MAX_REACT_QUERY_CACHE_TIME_MS, maxAge: MAX_REACT_QUERY_CACHE_TIME_MS,
persister: createSyncStoragePersister({ storage: localStorage }), persister: createSyncStoragePersister({ storage: localStorage }),
dehydrateOptions: sharedDehydrateOptions,
} }
export function QueryClientPersistProvider({ children }: PropsWithChildren): JSX.Element { export function QueryClientPersistProvider({ children }: PropsWithChildren): JSX.Element {
......
...@@ -3,11 +3,10 @@ import { Currency, CurrencyAmount, NativeCurrency, Token } from '@uniswap/sdk-co ...@@ -3,11 +3,10 @@ import { Currency, CurrencyAmount, NativeCurrency, Token } from '@uniswap/sdk-co
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import { PoolData } from 'appGraphql/data/pools/usePoolData' import { PoolData } from 'appGraphql/data/pools/usePoolData'
import { TimePeriod, gqlToCurrency, toHistoryDuration } from 'appGraphql/data/util' import { TimePeriod, gqlToCurrency, toHistoryDuration } from 'appGraphql/data/util'
import { TickTooltipContent } from 'components/Charts/ActiveLiquidityChart/TickTooltip'
import { ChartHeader } from 'components/Charts/ChartHeader' import { ChartHeader } from 'components/Charts/ChartHeader'
import { Chart, refitChartContentAtom } from 'components/Charts/ChartModel' import { Chart, refitChartContentAtom } from 'components/Charts/ChartModel'
import { LiquidityBarChartModel, useLiquidityBarData } from 'components/Charts/LiquidityChart' import { LiquidityBarChartModel, useLiquidityBarData } from 'components/Charts/LiquidityChart'
import { LiquidityBarData } from 'components/Charts/LiquidityChart/types' import { LiquidityBarData } from 'components/Charts/LiquidityChart/renderer'
import { ChartSkeleton } from 'components/Charts/LoadingState' import { ChartSkeleton } from 'components/Charts/LoadingState'
import { PriceChartData, PriceChartDelta, PriceChartModel } from 'components/Charts/PriceChart' import { PriceChartData, PriceChartDelta, PriceChartModel } from 'components/Charts/PriceChart'
import { VolumeChart } from 'components/Charts/VolumeChart' import { VolumeChart } from 'components/Charts/VolumeChart'
...@@ -339,6 +338,50 @@ const FadeInSubheader = styled(ThemedText.SubHeader)` ...@@ -339,6 +338,50 @@ const FadeInSubheader = styled(ThemedText.SubHeader)`
${textFadeIn} ${textFadeIn}
` `
function LiquidityTooltipDisplay({
data,
tokenADescriptor,
tokenBDescriptor,
currentTick,
}: {
data: LiquidityBarData
tokenADescriptor: string
tokenBDescriptor: string
currentTick?: number
}) {
const { t } = useTranslation()
const { formatNumberOrString } = useLocalizationContext()
if (!currentTick) {
return null
}
const displayValue0 =
data.tick >= currentTick
? formatNumberOrString({
value: data.amount0Locked,
type: NumberType.TokenQuantityStats,
})
: 0
const displayValue1 =
data.tick <= currentTick
? formatNumberOrString({
value: data.amount1Locked,
type: NumberType.TokenQuantityStats,
})
: 0
return (
<>
<ThemedText.BodySmall>
{t('liquidityPool.chart.tooltip.amount', { token: tokenADescriptor, amount: displayValue0 })}
</ThemedText.BodySmall>
<ThemedText.BodySmall>
{t('liquidityPool.chart.tooltip.amount', { token: tokenBDescriptor, amount: displayValue1 })}
</ThemedText.BodySmall>
</>
)
}
function LiquidityChart({ function LiquidityChart({
currencyA, currencyA,
currencyB, currencyB,
...@@ -385,7 +428,6 @@ function LiquidityChart({ ...@@ -385,7 +428,6 @@ function LiquidityChart({
highlightColor: theme.surface3, highlightColor: theme.surface3,
activeTick, activeTick,
activeTickProgress: tickData?.activeRangePercentage, activeTickProgress: tickData?.activeRangePercentage,
hideTooltipBorder: true,
} }
}, [activeTick, isReversed, theme, tickData]) }, [activeTick, isReversed, theme, tickData])
...@@ -398,20 +440,16 @@ function LiquidityChart({ ...@@ -398,20 +440,16 @@ function LiquidityChart({
height={PDP_CHART_HEIGHT_PX} height={PDP_CHART_HEIGHT_PX}
Model={LiquidityBarChartModel} Model={LiquidityBarChartModel}
params={params} params={params}
TooltipBody={({ data: crosshairData }: { data: LiquidityBarData }) => ( TooltipBody={({ data }: { data: LiquidityBarData }) => (
// TODO(WEB-3628): investigate potential off-by-one or subgraph issues causing calculated TVL issues on 1 bip pools // TODO(WEB-3628): investigate potential off-by-one or subgraph issues causing calculated TVL issues on 1 bip pools
// Also remove Error Boundary when its determined its not needed // Also remove Error Boundary when its determined its not needed
<ErrorBoundary fallback={() => null}> <ErrorBoundary fallback={() => null}>
{tickData?.activeRangeData && ( <LiquidityTooltipDisplay
<TickTooltipContent data={data}
baseCurrency={currencyB} tokenADescriptor={tokenADescriptor}
quoteCurrency={currencyA} tokenBDescriptor={tokenBDescriptor}
hoveredTick={crosshairData} currentTick={tickData?.activeRangeData?.tick}
currentTick={tickData?.activeRangeData?.tick} />
currentPrice={parseFloat(tickData?.activeRangeData?.price0)}
showQuoteCurrencyFirst={false}
/>
)}
</ErrorBoundary> </ErrorBoundary>
)} )}
> >
......
...@@ -166,9 +166,10 @@ const ContractsDropdownRow = ({ ...@@ -166,9 +166,10 @@ const ContractsDropdownRow = ({
const currency = tokens[0] && gqlToCurrency(tokens[0]) const currency = tokens[0] && gqlToCurrency(tokens[0])
const isPool = tokens.length === 2 const isPool = tokens.length === 2
const currencies = isPool && tokens[1] ? [currency, gqlToCurrency(tokens[1])] : [currency] const currencies = isPool && tokens[1] ? [currency, gqlToCurrency(tokens[1])] : [currency]
const isNative = address === NATIVE_CHAIN_ID || !address const isNative = address === NATIVE_CHAIN_ID
const explorerUrl = const explorerUrl =
chainId && chainId &&
address &&
getExplorerLink( getExplorerLink(
chainId, chainId,
address, address,
......
...@@ -146,7 +146,7 @@ function PoolTableHeader({ ...@@ -146,7 +146,7 @@ function PoolTableHeader({
<Flex width="100%"> <Flex width="100%">
<MouseoverTooltip <MouseoverTooltip
disabled={!HEADER_DESCRIPTIONS[category]} disabled={!HEADER_DESCRIPTIONS[category]}
size={TooltipSize.Small} size={TooltipSize.Max}
text={HEADER_DESCRIPTIONS[category]} text={HEADER_DESCRIPTIONS[category]}
placement="top" placement="top"
> >
......
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useOpenOffchainActivityModal } from 'components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal' import { useOpenOffchainActivityModal } from 'components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal'
import { import {
getFORTransactionToActivityQueryOptions,
getSignatureToActivityQueryOptions, getSignatureToActivityQueryOptions,
getTransactionToActivityQueryOptions, getTransactionToActivityQueryOptions,
} from 'components/AccountDrawer/MiniPortfolio/Activity/parseLocal' } from 'components/AccountDrawer/MiniPortfolio/Activity/parseLocal'
...@@ -15,7 +14,6 @@ import { useTranslation } from 'react-i18next' ...@@ -15,7 +14,6 @@ import { useTranslation } from 'react-i18next'
import { useOrder } from 'state/signatures/hooks' import { useOrder } from 'state/signatures/hooks'
import { useTransaction } from 'state/transactions/hooks' import { useTransaction } from 'state/transactions/hooks'
import { isPendingTx } from 'state/transactions/utils' import { isPendingTx } from 'state/transactions/utils'
import { EllipsisTamaguiStyle } from 'theme/components/styles'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { X } from 'ui/src/components/icons/X' import { X } from 'ui/src/components/icons/X'
import { BridgeIcon } from 'uniswap/src/components/CurrencyLogo/SplitLogo' import { BridgeIcon } from 'uniswap/src/components/CurrencyLogo/SplitLogo'
...@@ -23,11 +21,9 @@ import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__g ...@@ -23,11 +21,9 @@ import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__g
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId' import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FORTransaction } from 'uniswap/src/features/fiatOnRamp/types'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import noop from 'utilities/src/react/noop'
export function FailedNetworkSwitchPopup({ chainId, onClose }: { chainId: UniverseChainId; onClose: () => void }) { export function FailedNetworkSwitchPopup({ chainId, onClose }: { chainId: UniverseChainId; onClose: () => void }) {
const isSupportedChain = useIsSupportedChainId(chainId) const isSupportedChain = useIsSupportedChainId(chainId)
...@@ -82,7 +78,7 @@ function ActivityPopupContent({ activity, onClick, onClose }: ActivityPopupConte ...@@ -82,7 +78,7 @@ function ActivityPopupContent({ activity, onClick, onClose }: ActivityPopupConte
width: '100%', width: '100%',
}} }}
> >
<TouchableArea onPress={onClick} flex={1}> <TouchableArea onPress={onClick}>
<Flex row gap="$gap12" height={68} py="$spacing12" px="$spacing16"> <Flex row gap="$gap12" height={68} py="$spacing12" px="$spacing16">
{showPortfolioLogo ? ( {showPortfolioLogo ? (
<Flex> <Flex>
...@@ -98,11 +94,11 @@ function ActivityPopupContent({ activity, onClick, onClose }: ActivityPopupConte ...@@ -98,11 +94,11 @@ function ActivityPopupContent({ activity, onClick, onClose }: ActivityPopupConte
<AlertTriangleFilled color="$neutral2" size="32px" /> <AlertTriangleFilled color="$neutral2" size="32px" />
</Flex> </Flex>
)} )}
<Flex justifyContent="center" gap="$gap4" fill> <Flex justifyContent="center" gap="$gap4">
<Text variant="body2" color="$neutral1"> <Text variant="body2" color="$neutral1">
{activity.title} {activity.title}
</Text> </Text>
<Text variant="body3" color="$neutral2" {...EllipsisTamaguiStyle}> <Text variant="body3" color="$neutral2">
{activity.descriptor} {activity.descriptor}
</Text> </Text>
</Flex> </Flex>
...@@ -170,22 +166,3 @@ export function UniswapXOrderPopupContent({ orderHash, onClose }: { orderHash: s ...@@ -170,22 +166,3 @@ export function UniswapXOrderPopupContent({ orderHash, onClose }: { orderHash: s
return <ActivityPopupContent activity={activity} onClose={onClose} onClick={onClick} /> return <ActivityPopupContent activity={activity} onClose={onClose} onClick={onClick} />
} }
export function FORTransactionPopupContent({
transaction,
onClose,
}: {
transaction: FORTransaction
onClose: () => void
}) {
const { formatNumberOrString, convertFiatAmountFormatted } = useLocalizationContext()
const { data: activity } = useQuery(
getFORTransactionToActivityQueryOptions(transaction, formatNumberOrString, convertFiatAmountFormatted),
)
if (!activity || !transaction) {
return null
}
return <ActivityPopupContent activity={activity} onClose={onClose} onClick={noop} />
}
import { MismatchToastItem } from 'components/Popups/MismatchToastItem' import { MismatchToastItem } from 'components/Popups/MismatchToastItem'
import { import {
FORTransactionPopupContent,
FailedNetworkSwitchPopup, FailedNetworkSwitchPopup,
TransactionPopupContent, TransactionPopupContent,
UniswapXOrderPopupContent, UniswapXOrderPopupContent,
...@@ -56,9 +55,6 @@ export function PopupItem({ content, onClose }: { content: PopupContent; popKey: ...@@ -56,9 +55,6 @@ export function PopupItem({ content, onClose }: { content: PopupContent; popKey:
case PopupType.Mismatch: { case PopupType.Mismatch: {
return <MismatchToastItem onDismiss={onClose} /> return <MismatchToastItem onDismiss={onClose} />
} }
case PopupType.FORTransaction: {
return <FORTransactionPopupContent transaction={content.transaction} onClose={onClose} />
}
} }
} }
......
...@@ -22,8 +22,8 @@ class PopupRegistry { ...@@ -22,8 +22,8 @@ class PopupRegistry {
} }
removePopup(key: string): void { removePopup(key: string): void {
toast.dismiss(this.popupKeyToId.get(key))
this.popupKeyToId.delete(key) this.popupKeyToId.delete(key)
toast.dismiss(this.popupKeyToId.get(key))
} }
} }
......
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FORTransaction } from 'uniswap/src/features/fiatOnRamp/types'
import { CurrencyId } from 'uniswap/src/types/currency'
import { SwapTab } from 'uniswap/src/types/screens/interface' import { SwapTab } from 'uniswap/src/types/screens/interface'
export enum PopupType { export enum PopupType {
...@@ -10,7 +8,6 @@ export enum PopupType { ...@@ -10,7 +8,6 @@ export enum PopupType {
SwitchNetwork = 'switchNetwork', SwitchNetwork = 'switchNetwork',
Bridge = 'bridge', Bridge = 'bridge',
Mismatch = 'mismatch', Mismatch = 'mismatch',
FORTransaction = 'forTransaction',
} }
export type PopupContent = export type PopupContent =
...@@ -39,8 +36,3 @@ export type PopupContent = ...@@ -39,8 +36,3 @@ export type PopupContent =
| { | {
type: PopupType.Mismatch type: PopupType.Mismatch
} }
| {
type: PopupType.FORTransaction
transaction: FORTransaction
currencyId: CurrencyId
}
import { InterfaceElementName } from '@uniswap/analytics-events' import { InterfaceElementName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import Loader from 'components/Icons/LoadingSpinner'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { MenuItem } from 'components/SearchModal/styled' import { MenuItem } from 'components/SearchModal/styled'
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
import { useAccount } from 'hooks/useAccount'
import { useTokenBalances } from 'hooks/useTokenBalances' import { useTokenBalances } from 'hooks/useTokenBalances'
import { CSSProperties } from 'react' import { CSSProperties } from 'react'
import { TokenFromList } from 'state/lists/tokenFromList' import { TokenFromList } from 'state/lists/tokenFromList'
...@@ -33,7 +35,6 @@ const TextOverflowStyle = { ...@@ -33,7 +35,6 @@ const TextOverflowStyle = {
const StyledBalanceText = styled(Text, { const StyledBalanceText = styled(Text, {
...TextOverflowStyle, ...TextOverflowStyle,
maxWidth: '80px', maxWidth: '80px',
textAlign: 'right',
}) })
const CurrencyName = styled(Text, TextOverflowStyle) const CurrencyName = styled(Text, TextOverflowStyle)
...@@ -52,6 +53,19 @@ const Tag = styled(Text, { ...@@ -52,6 +53,19 @@ const Tag = styled(Text, {
mr: '$spacing4', mr: '$spacing4',
}) })
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
const { formatNumberOrString } = useLocalizationContext()
return (
<StyledBalanceText>
{formatNumberOrString({
value: balance.toExact(),
type: NumberType.TokenNonTx,
})}
</StyledBalanceText>
)
}
function TokenTags({ currency }: { currency: Currency }) { function TokenTags({ currency }: { currency: Currency }) {
if (!(currency instanceof TokenFromList)) { if (!(currency instanceof TokenFromList)) {
return null return null
...@@ -95,7 +109,6 @@ export function CurrencyRow({ ...@@ -95,7 +109,6 @@ export function CurrencyRow({
otherSelected, otherSelected,
style, style,
showCurrencyAmount, showCurrencyAmount,
showUsdValue,
eventProperties, eventProperties,
balance, balance,
disabled, disabled,
...@@ -108,15 +121,14 @@ export function CurrencyRow({ ...@@ -108,15 +121,14 @@ export function CurrencyRow({
otherSelected: boolean otherSelected: boolean
style?: CSSProperties style?: CSSProperties
showCurrencyAmount?: boolean showCurrencyAmount?: boolean
showUsdValue?: boolean
eventProperties: Record<string, unknown> eventProperties: Record<string, unknown>
balance?: CurrencyAmount<Currency> balance?: CurrencyAmount<Currency>
disabled?: boolean disabled?: boolean
tooltip?: string tooltip?: string
showAddress?: boolean showAddress?: boolean
}) { }) {
const account = useAccount()
const { currency } = currencyInfo const { currency } = currencyInfo
const { convertFiatAmountFormatted, formatNumberOrString } = useLocalizationContext()
const key = currencyListRowKey(currency) const key = currencyListRowKey(currency)
const { tokenWarningDismissed: customAdded } = useDismissedTokenWarnings(currency) const { tokenWarningDismissed: customAdded } = useDismissedTokenWarnings(currency)
...@@ -125,8 +137,7 @@ export function CurrencyRow({ ...@@ -125,8 +137,7 @@ export function CurrencyRow({
const blockedTokenOpacity = '0.6' const blockedTokenOpacity = '0.6'
const { balanceMap } = useTokenBalances({ cacheOnly: true }) const { balanceMap } = useTokenBalances({ cacheOnly: true })
const { usdValue, balance: cachedBalance } = balanceMap[currencyKey(currency)] ?? {} const balanceUSD = balanceMap[currencyKey(currency)]?.usdValue
const tokenBalance = balance ? balance.toExact() : cachedBalance
const Wrapper = tooltip ? MouseoverTooltip : RowWrapper const Wrapper = tooltip ? MouseoverTooltip : RowWrapper
...@@ -136,7 +147,7 @@ export function CurrencyRow({ ...@@ -136,7 +147,7 @@ export function CurrencyRow({
logPress logPress
logKeyPress logKeyPress
eventOnTrigger={UniswapEventName.TokenSelected} eventOnTrigger={UniswapEventName.TokenSelected}
properties={{ is_imported_by_user: customAdded, ...eventProperties, token_balance_usd: usdValue }} properties={{ is_imported_by_user: customAdded, ...eventProperties, token_balance_usd: balanceUSD }}
element={InterfaceElementName.TOKEN_SELECTOR_ROW} element={InterfaceElementName.TOKEN_SELECTOR_ROW}
> >
<Wrapper <Wrapper
...@@ -176,21 +187,11 @@ export function CurrencyRow({ ...@@ -176,21 +187,11 @@ export function CurrencyRow({
<TokenTags currency={currency} /> <TokenTags currency={currency} />
</Flex> </Flex>
</Flex> </Flex>
<Flex alignSelf="center" justifyContent="flex-end"> {showCurrencyAmount && (
{showUsdValue && usdValue ? ( <Flex row alignSelf="center" justifyContent="flex-end">
<StyledBalanceText variant="body4" color="$neutral1"> {account.isConnected ? balance ? <Balance balance={balance} /> : <Loader /> : null}
{convertFiatAmountFormatted(usdValue, NumberType.FiatStandard)} </Flex>
</StyledBalanceText> )}
) : null}
{showCurrencyAmount && tokenBalance ? (
<StyledBalanceText variant="body4" color="$neutral2">
{formatNumberOrString({
value: tokenBalance,
type: NumberType.TokenNonTx,
})}
</StyledBalanceText>
) : null}
</Flex>
</MenuItem> </MenuItem>
</Wrapper> </Wrapper>
</Trace> </Trace>
......
...@@ -12,9 +12,8 @@ const StyledDownArrow = styled(ArrowChangeDown)<{ $noColor?: boolean }>` ...@@ -12,9 +12,8 @@ const StyledDownArrow = styled(ArrowChangeDown)<{ $noColor?: boolean }>`
$noColor ? theme.neutral2 : theme.darkMode ? colorsDark.statusCritical : colorsLight.statusCritical}; $noColor ? theme.neutral2 : theme.darkMode ? colorsDark.statusCritical : colorsLight.statusCritical};
` `
export function calculateDelta(start: number, current: number): number | undefined { export function calculateDelta(start: number, current: number) {
const delta = (current / start - 1) * 100 return (current / start - 1) * 100
return isValidDelta(delta) ? delta : undefined
} }
function isValidDelta(delta: number | null | undefined): delta is number { function isValidDelta(delta: number | null | undefined): delta is number {
......
...@@ -125,7 +125,7 @@ function TokenTableHeader({ ...@@ -125,7 +125,7 @@ function TokenTableHeader({
<Flex width="100%"> <Flex width="100%">
<MouseoverTooltip <MouseoverTooltip
disabled={!HEADER_DESCRIPTIONS[category]} disabled={!HEADER_DESCRIPTIONS[category]}
size={TooltipSize.Small} size={TooltipSize.Max}
text={HEADER_DESCRIPTIONS[category]} text={HEADER_DESCRIPTIONS[category]}
placement="top" placement="top"
> >
......
...@@ -144,7 +144,7 @@ export function useOrderedConnections(options?: { showSecondaryConnectors?: bool ...@@ -144,7 +144,7 @@ export function useOrderedConnections(options?: { showSecondaryConnectors?: bool
// Injected connectors should appear next in the list, as the user intentionally installed/uses them. // Injected connectors should appear next in the list, as the user intentionally installed/uses them.
if (showSecondaryConnectors) { if (showSecondaryConnectors) {
if (isMobileWeb && isEmbeddedWalletEnabled) { if (isMobileWeb) {
orderedConnectors.push(embeddedWalletConnector) orderedConnectors.push(embeddedWalletConnector)
} }
const secondaryConnectors = [walletConnectConnector, coinbaseSdkConnector] const secondaryConnectors = [walletConnectConnector, coinbaseSdkConnector]
......
import { createWeb3Provider } from 'components/Web3Provider/createWeb3Provider'
import { wagmiConfig } from 'components/Web3Provider/wagmiConfig'
/**
* Web3Provider variant for Jest/Playwright.
* - Does NOT attempt to reconnect on mount (avoids wallet pop-ups/mocks).
*
* Tests should import this component instead of the default production provider.
*/
const TestWeb3Provider = createWeb3Provider({
wagmiConfig,
reconnectOnMount: false,
includeCapabilitiesEffects: false,
})
export default TestWeb3Provider
...@@ -24,5 +24,3 @@ export const recentConnectorIdAtom = atomWithStorage<string | undefined>('recent ...@@ -24,5 +24,3 @@ export const recentConnectorIdAtom = atomWithStorage<string | undefined>('recent
export function useRecentConnectorId() { export function useRecentConnectorId() {
return useAtomValue(recentConnectorIdAtom) return useAtomValue(recentConnectorIdAtom)
} }
export const PLAYWRIGHT_CONNECT_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
import React, { ReactNode } from 'react'
import type { Config } from 'wagmi'
import { WagmiProvider } from 'wagmi'
import { ConnectionProvider } from 'hooks/useConnect'
import { useWalletCapabilitiesStateEffect } from 'state/walletCapabilities/hooks/useWalletCapabilitiesStateEffect'
export function createWeb3Provider(params: {
wagmiConfig: Config
reconnectOnMount?: boolean
includeCapabilitiesEffects?: boolean
}) {
const { wagmiConfig, reconnectOnMount = true, includeCapabilitiesEffects = true } = params
const WalletCapabilitiesEffects: React.FC = () => {
useWalletCapabilitiesStateEffect()
return null
}
const Provider = ({ children }: { children: ReactNode }) => (
<WagmiProvider config={wagmiConfig} reconnectOnMount={reconnectOnMount}>
<ConnectionProvider>
{includeCapabilitiesEffects && <WalletCapabilitiesEffects />}
{children}
</ConnectionProvider>
</WagmiProvider>
)
Provider.displayName = 'Web3Provider'
return Provider
}
import { Web3Provider as EthersWeb3Provider, ExternalProvider } from '@ethersproject/providers' import { Web3Provider as EthersWeb3Provider, ExternalProvider } from '@ethersproject/providers'
import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events' import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
import { UNISWAP_EXTENSION_CONNECTOR_NAME, recentConnectorIdAtom } from 'components/Web3Provider/constants' import { UNISWAP_EXTENSION_CONNECTOR_NAME, recentConnectorIdAtom } from 'components/Web3Provider/constants'
import { createWeb3Provider } from 'components/Web3Provider/createWeb3Provider'
import { wagmiConfig } from 'components/Web3Provider/wagmiConfig' import { wagmiConfig } from 'components/Web3Provider/wagmiConfig'
import { walletTypeToAmplitudeWalletType } from 'components/Web3Provider/walletConnect' import { walletTypeToAmplitudeWalletType } from 'components/Web3Provider/walletConnect'
import { RPC_PROVIDERS } from 'constants/providers' import { RPC_PROVIDERS } from 'constants/providers'
import { useAccount } from 'hooks/useAccount' import { useAccount } from 'hooks/useAccount'
import { ConnectionProvider } from 'hooks/useConnect'
import { useEthersWeb3Provider } from 'hooks/useEthersProvider' import { useEthersWeb3Provider } from 'hooks/useEthersProvider'
import usePrevious from 'hooks/usePrevious' import usePrevious from 'hooks/usePrevious'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { useEffect } from 'react' import { ReactNode, useEffect } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { useWalletCapabilitiesStateEffect } from 'state/walletCapabilities/hooks/useWalletCapabilitiesStateEffect'
import { useConnectedWallets } from 'state/wallets/hooks' import { useConnectedWallets } from 'state/wallets/hooks'
import { CONVERSION_EVENTS } from 'uniswap/src/data/rest/conversionTracking/constants' import { CONVERSION_EVENTS } from 'uniswap/src/data/rest/conversionTracking/constants'
import { useConversionTracking } from 'uniswap/src/data/rest/conversionTracking/useConversionTracking' import { useConversionTracking } from 'uniswap/src/data/rest/conversionTracking/useConversionTracking'
...@@ -20,16 +21,23 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' ...@@ -20,16 +21,23 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { setUserProperty } from 'uniswap/src/features/telemetry/user' import { setUserProperty } from 'uniswap/src/features/telemetry/user'
import { isTestEnv } from 'utilities/src/environment/env'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
import { getCurrentPageFromLocation } from 'utils/urlRoutes' import { getCurrentPageFromLocation } from 'utils/urlRoutes'
import { WalletType, getWalletMeta } from 'utils/walletMeta' import { WalletType, getWalletMeta } from 'utils/walletMeta'
import { useAccount as useAccountWagmi } from 'wagmi' import { WagmiProvider, useAccount as useAccountWagmi } from 'wagmi'
// Production Web3Provider – always reconnects on mount and runs capability effects. export default function Web3Provider({ children }: { children: ReactNode }) {
const Web3Provider = createWeb3Provider({ wagmiConfig }) return (
<WagmiProvider config={wagmiConfig}>
export default Web3Provider <ConnectionProvider>
<WalletCapabilitiesEffects />
{children}
</ConnectionProvider>
</WagmiProvider>
)
}
/** A component to run hooks under the Web3ReactProvider context. */ /** A component to run hooks under the Web3ReactProvider context. */
export function Web3ProviderUpdater() { export function Web3ProviderUpdater() {
...@@ -189,3 +197,17 @@ function trace(event: any) { ...@@ -189,3 +197,17 @@ function trace(event: any) {
const { method, id, params } = event.request const { method, id, params } = event.request
logger.debug('Web3Provider', 'provider', 'trace', { method, id, params }) logger.debug('Web3Provider', 'provider', 'trace', { method, id, params })
} }
/**
* WalletCapabilitiesEffectsInner -- handles the effects related to wallet capabilities
* @returns null
*/
const WalletCapabilitiesEffectsInner: React.FC = () => {
// get the wallet capabilities for the current account on connect (and reset on disconnect)
useWalletCapabilitiesStateEffect()
return null
}
// we don't want to run the smart account wallet effects in tests
const WalletCapabilitiesEffects: React.FC = isTestEnv() ? () => null : WalletCapabilitiesEffectsInner
import { PLAYWRIGHT_CONNECT_ADDRESS } from 'components/Web3Provider/constants'
import { wagmiConfig } from 'components/Web3Provider/wagmiConfig'
import { isPlaywrightEnv } from 'utilities/src/environment/env'
import { isAddress } from 'viem'
import { connect } from 'wagmi/actions'
import { injected, mock } from 'wagmi/connectors'
// Cypress runner marks window.Cypress – rely on support/commands to set eagerlyConnect flag
if ((window as any).Cypress?.eagerlyConnect) {
connect(wagmiConfig, { connector: injected() })
}
export function setupWagmiAutoConnect() {
// Cypress runner marks window.Cypress – rely on support/commands to set eagerlyConnect flag
if ((window as any).Cypress?.eagerlyConnect) {
connect(wagmiConfig, { connector: injected() })
}
const isEagerlyConnect = !window.location.search.includes('eagerlyConnect=false')
const eagerlyConnectAddress = window.location.search.includes('eagerlyConnectAddress=')
? window.location.search.split('eagerlyConnectAddress=')[1]
: undefined
// Automatically connect if running under Playwright (used by E2E tests)
if (isPlaywrightEnv() && isEagerlyConnect) {
// setTimeout avoids immediate disconnection caused by race condition in wagmi mock connector
setTimeout(() => {
connect(wagmiConfig, {
connector: mock({
features: {},
accounts: [
eagerlyConnectAddress && isAddress(eagerlyConnectAddress)
? eagerlyConnectAddress
: PLAYWRIGHT_CONNECT_ADDRESS,
],
}),
})
}, 1)
}
}
import { PLAYWRIGHT_CONNECT_ADDRESS } from 'components/Web3Provider/constants'
import { injectedWithFallback } from 'components/Web3Provider/injectedWithFallback' import { injectedWithFallback } from 'components/Web3Provider/injectedWithFallback'
import { WC_PARAMS } from 'components/Web3Provider/walletConnect' import { WC_PARAMS } from 'components/Web3Provider/walletConnect'
import { embeddedWallet } from 'connection/EmbeddedWalletConnector' import { embeddedWallet } from 'connection/EmbeddedWalletConnector'
...@@ -9,9 +8,16 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type ...@@ -9,9 +8,16 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type
import { isTestnetChain } from 'uniswap/src/features/chains/utils' import { isTestnetChain } from 'uniswap/src/features/chains/utils'
import { isPlaywrightEnv } from 'utilities/src/environment/env' import { isPlaywrightEnv } from 'utilities/src/environment/env'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { Chain, createClient } from 'viem' import { Chain, createClient, isAddress } from 'viem'
import { Config, createConfig, fallback, http } from 'wagmi' import { createConfig, fallback, http } from 'wagmi'
import { coinbaseWallet, mock, safe, walletConnect } from 'wagmi/connectors' import { connect } from 'wagmi/actions'
import { coinbaseWallet, injected, mock, safe, walletConnect } from 'wagmi/connectors'
declare module 'wagmi' {
interface Register {
config: typeof wagmiConfig
}
}
export const orderedTransportUrls = (chain: ReturnType<typeof getChainInfo>): string[] => { export const orderedTransportUrls = (chain: ReturnType<typeof getChainInfo>): string[] => {
const orderedRpcUrls = [ const orderedRpcUrls = [
...@@ -24,64 +30,50 @@ export const orderedTransportUrls = (chain: ReturnType<typeof getChainInfo>): st ...@@ -24,64 +30,50 @@ export const orderedTransportUrls = (chain: ReturnType<typeof getChainInfo>): st
return Array.from(new Set(orderedRpcUrls.filter(Boolean))) return Array.from(new Set(orderedRpcUrls.filter(Boolean)))
} }
function createWagmiConnectors(params: { const baseConnectors = [
/** If `true`, appends the wagmi `mock` connector. Used in Playwright. */ injectedWithFallback(),
includeMockConnector: boolean walletConnect(WC_PARAMS),
}): any[] { embeddedWallet(),
const { includeMockConnector } = params coinbaseWallet({
appName: 'Uniswap',
const baseConnectors = [ // CB SDK doesn't pass the parent origin context to their passkey site
injectedWithFallback(), // Flagged to CB team and can remove UNISWAP_WEB_URL once fixed
walletConnect(WC_PARAMS), appLogoUrl: `${UNISWAP_WEB_URL}${UNISWAP_LOGO}`,
embeddedWallet(), reloadOnDisconnect: false,
coinbaseWallet({ enableMobileWalletLink: true,
appName: 'Uniswap', }),
// CB SDK doesn't pass the parent origin context to their passkey site safe(),
// Flagged to CB team and can remove UNISWAP_WEB_URL once fixed ]
appLogoUrl: `${UNISWAP_WEB_URL}${UNISWAP_LOGO}`,
reloadOnDisconnect: false,
}),
safe(),
]
return includeMockConnector
? [
...baseConnectors,
mock({
features: {},
accounts: [PLAYWRIGHT_CONNECT_ADDRESS],
}),
]
: baseConnectors
}
function createWagmiConfig(params: { // Only add mock connector in Playwright environment
/** The connector list to use. */ const connectors = isPlaywrightEnv()
connectors: any[] ? [
/** Optional custom `onFetchResponse` handler – defaults to `defaultOnFetchResponse`. */ ...baseConnectors,
onFetchResponse?: (response: Response, chain: Chain, url: string) => void mock({
}): Config { features: {},
const { connectors, onFetchResponse = defaultOnFetchResponse } = params accounts: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'],
}),
]
: baseConnectors
return createConfig({ export const wagmiConfig = createConfig({
chains: [getChainInfo(UniverseChainId.Mainnet), ...ALL_CHAIN_IDS.map(getChainInfo)], chains: [getChainInfo(UniverseChainId.Mainnet), ...ALL_CHAIN_IDS.map(getChainInfo)],
connectors, connectors,
client({ chain }) { client({ chain }) {
return createClient({ return createClient({
chain, chain,
batch: { multicall: true }, batch: { multicall: true },
pollingInterval: 12_000, pollingInterval: 12_000,
transport: fallback( transport: fallback(
orderedTransportUrls(chain).map((url) => orderedTransportUrls(chain).map((url) =>
http(url, { onFetchResponse: (response) => onFetchResponse(response, chain, url) }), http(url, { onFetchResponse: (response) => onFetchResponse(response, chain, url) }),
),
), ),
}) ),
}, })
}) },
} })
const defaultOnFetchResponse = (response: Response, chain: Chain, url: string) => { const onFetchResponse = (response: Response, chain: Chain, url: string) => {
if (response.status !== 200) { if (response.status !== 200) {
const message = `RPC provider returned non-200 status: ${response.status}` const message = `RPC provider returned non-200 status: ${response.status}`
...@@ -109,15 +101,29 @@ const defaultOnFetchResponse = (response: Response, chain: Chain, url: string) = ...@@ -109,15 +101,29 @@ const defaultOnFetchResponse = (response: Response, chain: Chain, url: string) =
} }
} }
const defaultConnectors = createWagmiConnectors({ // Automatically connect if running in Cypress environment
includeMockConnector: isPlaywrightEnv(), if ((window as any).Cypress?.eagerlyConnect) {
}) connect(wagmiConfig, { connector: injected() })
}
export const wagmiConfig: Config = createWagmiConfig({ connectors: defaultConnectors }) const isEagerlyConnect = !window.location.search.includes('eagerlyConnect=false')
const eagerlyConnectAddress = window.location.search.includes('eagerlyConnectAddress=')
? window.location.search.split('eagerlyConnectAddress=')[1]
: undefined
declare module 'wagmi' { // Automatically connect if running in Playwright environment
interface Register { if (isPlaywrightEnv() && isEagerlyConnect) {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports // setTimeout is needed to avoid disconnection
config: typeof wagmiConfig setTimeout(() => {
} connect(wagmiConfig, {
connector: mock({
features: {},
accounts: [
eagerlyConnectAddress && isAddress(eagerlyConnectAddress)
? eagerlyConnectAddress
: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
],
}),
})
}, 1)
} }
...@@ -11,7 +11,7 @@ import { Allowance, AllowanceState } from 'hooks/usePermit2Allowance' ...@@ -11,7 +11,7 @@ import { Allowance, AllowanceState } from 'hooks/usePermit2Allowance'
import { SwapResult } from 'hooks/useSwapCallback' import { SwapResult } from 'hooks/useSwapCallback'
import styled from 'lib/styled-components' import styled from 'lib/styled-components'
import ms from 'ms' import ms from 'ms'
import { PropsWithChildren, ReactNode, useMemo, useState } from 'react' import { ReactNode, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { InterfaceTrade, LimitOrderTrade, RouterPreference } from 'state/routing/types' import { InterfaceTrade, LimitOrderTrade, RouterPreference } from 'state/routing/types'
import { isClassicTrade, isLimitTrade } from 'state/routing/utils' import { isClassicTrade, isLimitTrade } from 'state/routing/utils'
...@@ -69,24 +69,13 @@ interface HelpLink { ...@@ -69,24 +69,13 @@ interface HelpLink {
url: string url: string
} }
// TODO: Extract to Spore ExpandoRow component (WEB-7906) function DropdownController({ open, onClick }: { open: boolean; onClick: () => void }) {
export function DropdownController({
open,
onClick,
children,
}: PropsWithChildren & { open: boolean; onClick: () => void }) {
return ( return (
<DropdownButton onClick={onClick}> <DropdownButton onClick={onClick}>
<Separator /> <Separator />
<DropdownControllerWrapper> <DropdownControllerWrapper>
<ThemedText.BodySmall color="neutral2"> <ThemedText.BodySmall color="neutral2">
{children ? ( {open ? <Trans i18nKey="common.showLess.button" /> : <Trans i18nKey="common.showMore.button" />}
children
) : open ? (
<Trans i18nKey="common.showLess.button" />
) : (
<Trans i18nKey="common.showMore.button" />
)}
</ThemedText.BodySmall> </ThemedText.BodySmall>
{open ? <ExpandoIconOpened /> : <ExpandoIconClosed />} {open ? <ExpandoIconOpened /> : <ExpandoIconClosed />}
</DropdownControllerWrapper> </DropdownControllerWrapper>
......
...@@ -2,14 +2,12 @@ import { ReactNode } from 'react' ...@@ -2,14 +2,12 @@ import { ReactNode } from 'react'
import { Flex, styled as TamaguiStyled, Text } from 'ui/src' import { Flex, styled as TamaguiStyled, Text } from 'ui/src'
import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled' import { AlertTriangleFilled } from 'ui/src/components/icons/AlertTriangleFilled'
export const PAGE_WRAPPER_MAX_WIDTH = 480
export const PageWrapper = TamaguiStyled(Flex, { export const PageWrapper = TamaguiStyled(Flex, {
pt: '$spacing60', pt: '$spacing60',
px: '$spacing8', px: '$spacing8',
pb: '$spacing40', pb: '$spacing40',
width: '100%', width: '100%',
maxWidth: PAGE_WRAPPER_MAX_WIDTH, maxWidth: 480,
$lg: { $lg: {
pt: '$spacing48', pt: '$spacing48',
}, },
......
...@@ -57,7 +57,7 @@ export function ConnectionProvider({ children }: PropsWithChildren) { ...@@ -57,7 +57,7 @@ export function ConnectionProvider({ children }: PropsWithChildren) {
* Wraps wagmi.useConnect in a singleton provider to provide the same connect state to all callers. * Wraps wagmi.useConnect in a singleton provider to provide the same connect state to all callers.
* @see {@link https://wagmi.sh/react/api/hooks/useConnect} * @see {@link https://wagmi.sh/react/api/hooks/useConnect}
*/ */
export function useConnect(): UseConnectReturnType<ResolvedRegister['config']> { export function useConnect() {
const value = useContext(ConnectionContext) const value = useContext(ConnectionContext)
if (!value) { if (!value) {
throw new Error('useConnect must be used within a ConnectionProvider') throw new Error('useConnect must be used within a ConnectionProvider')
......
import { Web3Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import { useAccount } from 'hooks/useAccount' import { useAccount } from 'hooks/useAccount'
import { useMemo } from 'react' import { useMemo } from 'react'
import type { Chain, Client, Transport } from 'viem' import { UniverseChainInfo } from 'uniswap/src/features/chains/types'
import type { Client, Transport } from 'viem'
import { useClient, useConnectorClient } from 'wagmi' import { useClient, useConnectorClient } from 'wagmi'
const providers = new WeakMap<Client, Web3Provider>() const providers = new WeakMap<Client, Web3Provider>()
export function clientToProvider(client?: Client<Transport, Chain>, chainId?: number) { export function clientToProvider(client?: Client<Transport, UniverseChainInfo>, chainId?: number) {
if (!client) { if (!client) {
return undefined return undefined
} }
......
import { Web3Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import { useMemo } from 'react' import { useMemo } from 'react'
import type { Account, Chain, Client, Transport } from 'viem' import { UniverseChainInfo } from 'uniswap/src/features/chains/types'
import type { Account, Client, Transport } from 'viem'
import { useConnectorClient } from 'wagmi' import { useConnectorClient } from 'wagmi'
function clientToSigner(client?: Client<Transport, Chain, Account>) { function clientToSigner(client?: Client<Transport, UniverseChainInfo, Account>) {
if (!client || !client.chain) { if (!client || !client.chain) {
return undefined return undefined
} }
......
import { TransactionRequest } from '@ethersproject/abstract-provider' import { TransactionRequest } from '@ethersproject/abstract-provider'
import { UseQueryResult, useQuery } from '@tanstack/react-query'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { ReactQueryCacheKey } from 'utilities/src/reactQuery/cache' import { useAsyncData } from 'utilities/src/react/hooks'
import { queryWithoutCache } from 'utilities/src/reactQuery/queryOptions'
enum FeeType { enum FeeType {
Legacy = 'legacy', Legacy = 'legacy',
...@@ -69,8 +67,13 @@ export enum GasSpeed { ...@@ -69,8 +67,13 @@ export enum GasSpeed {
Urgent = 'urgent', Urgent = 'urgent',
} }
export function useTransactionGasFee(tx?: TransactionRequest, speed: GasSpeed = GasSpeed.Urgent): GasFeeResult { export function useTransactionGasFee(
const { data, isLoading } = useGasFeeQuery(tx) tx?: TransactionRequest,
speed: GasSpeed = GasSpeed.Urgent,
skip: boolean = !tx,
): GasFeeResult {
const gasFeeFetcher = useGasFeeQuery(tx, skip)
const { data, isLoading } = useAsyncData(gasFeeFetcher)
return useMemo(() => { return useMemo(() => {
if (!data) { if (!data) {
...@@ -102,10 +105,12 @@ const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_BASE_API_URL ...@@ -102,10 +105,12 @@ const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_BASE_API_URL
const isErrorResponse = (res: Response, gasFee: GasFeeResponse): gasFee is GasFeeResponseError => const isErrorResponse = (res: Response, gasFee: GasFeeResponse): gasFee is GasFeeResponseError =>
res.status < 200 || res.status > 202 res.status < 200 || res.status > 202
function useGasFeeQuery(tx?: TransactionRequest): UseQueryResult<GasFeeResponseEip1559 | GasFeeResponseLegacy | null> { function useGasFeeQuery(tx?: TransactionRequest, skip: boolean = !tx) {
const skip = !tx
const gasFeeFetcher = useCallback(async () => { const gasFeeFetcher = useCallback(async () => {
if (skip) {
return undefined
}
const res = await fetch(`${UNISWAP_API_URL}/v1/gas-fee`, { const res = await fetch(`${UNISWAP_API_URL}/v1/gas-fee`, {
method: 'POST', method: 'POST',
body: JSON.stringify(tx), body: JSON.stringify(tx),
...@@ -114,17 +119,11 @@ function useGasFeeQuery(tx?: TransactionRequest): UseQueryResult<GasFeeResponseE ...@@ -114,17 +119,11 @@ function useGasFeeQuery(tx?: TransactionRequest): UseQueryResult<GasFeeResponseE
const body = (await res.json()) as GasFeeResponse const body = (await res.json()) as GasFeeResponse
if (isErrorResponse(res, body)) { if (isErrorResponse(res, body)) {
return null return undefined
} }
return body return body
}, [tx]) }, [skip, tx])
return useQuery( return gasFeeFetcher
queryWithoutCache({
queryKey: [ReactQueryCacheKey.WebTransactionGasFee, tx],
queryFn: gasFeeFetcher,
enabled: !skip,
}),
)
} }
...@@ -20,7 +20,7 @@ test('should increase liquidity of a position', async ({ page, anvil }) => { ...@@ -20,7 +20,7 @@ test('should increase liquidity of a position', async ({ page, anvil }) => {
await page.getByTestId(TestID.AmountInputIn).nth(1).click() await page.getByTestId(TestID.AmountInputIn).nth(1).click()
await page.getByTestId(TestID.AmountInputIn).nth(1).fill('1') await page.getByTestId(TestID.AmountInputIn).nth(1).fill('1')
await page.getByRole('button', { name: 'Review' }).click() await page.getByRole('button', { name: 'Add' }).click()
await page.getByRole('button', { name: 'Confirm' }).click() await page.getByRole('button', { name: 'Confirm' }).click()
await expect(page.getByText('Approved')).toBeVisible() await expect(page.getByText('Approved')).toBeVisible()
}) })
...@@ -32,6 +32,7 @@ const tabs = [ ...@@ -32,6 +32,7 @@ const tabs = [
dropdown: [ dropdown: [
{ label: 'Swap', path: '/swap' }, { label: 'Swap', path: '/swap' },
{ label: 'Limit', path: '/limit' }, { label: 'Limit', path: '/limit' },
{ label: 'Send', path: '/send' },
{ label: 'Buy', path: '/buy' }, { label: 'Buy', path: '/buy' },
], ],
}, },
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment