ci(release): publish latest release

parent 7e476ac3
---
description:
globs:
alwaysApply: true
---
# Frontend Testing Strategy
When writing tests for our frontend monorepo, follow these principles:
## Core Philosophy
- Test behaviors, not implementations
- Optimize for confidence, readability, and maintainability
- Follow the testing pyramid: unit tests > integration tests > E2E tests
- Reference existing tests for examples of our team's best practices. For example: [useBooleanState.test.ts](mdc:packages/utilities/src/react/useBooleanState.test.ts) [useSwapNetworkNotification.test.ts](mdc:packages/uniswap/src/features/transactions/swap/form/hooks/useSwapNetworkNotification.test.ts)
## Test Structure & Patterns
### Unit Tests
- Test individual functions, hooks, and components in isolation
- Use Jest and React Testing Library/React Native Testing Library
- Follow Arrange-Act-Assert pattern
- Mock dependencies appropriately
- Name tests descriptively: `it('should [behavior] when [condition]')`
```typescript
// Component test example
test('should display error when form submission fails', async () => {
// Arrange
const errorMessage = 'Invalid credentials';
const mockSubmit = jest.fn().mockRejectedValue(new Error(errorMessage));
render(<LoginForm onSubmit={mockSubmit} />);
// Act
await userEvent.type(screen.getByLabelText(/email/i), 'user@example.com');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
await userEvent.click(screen.getByRole('button', { name: /login/i }));
// Assert
expect(await screen.findByText(errorMessage)).toBeInTheDocument();
expect(mockSubmit).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'password123'
});
});
// Hook test example
test('should increment counter when increment function is called', () => {
// Arrange
const { result } = renderHook(() => useCounter(0));
// Act
act(() => {
result.current.increment();
});
// Assert
expect(result.current.count).toBe(1);
});
```
### Integration Tests
- Test interactions between multiple components
- Focus on user workflows and component communication
- Use React Testing Library's user-event
- Minimize mocking to test actual integrations
## Best Practices
### Test Data Management
- Create factory functions for generating test data
- Use TypeScript to ensure test data matches expected interfaces
```typescript
const createUser = (overrides = {}): User => ({
id: uuid(),
name: 'Test User',
email: 'test@example.com',
role: 'user',
...overrides
});
```
### Mocking Strategy
- Prefer manual mocks over automatic mocks
- Mock at the boundary of your system (API calls, third-party services)
- For React Native, mock native modules using Jest's mock functions
```typescript
jest.mock('packages/uniswap/...', () => ({
fetchUserData: jest.fn().mockResolvedValue({
id: '123',
name: 'Test User',
email: 'test@example.com'
})
}));
```
### Coverage Guidelines
- Aim for 80% coverage overall, 90%+ for critical business logic
- When requested to write UI tests, cover all user-facing components with at least basic rendering tests
- Test error states and edge cases; when unceratin about these states and cases, stop and ask for clarification before continuing
- Focus on quality over quantity - 5 well-written tests > 20 poor tests
## Monorepo Considerations
- Use the same path import strategy found in other files; everything should pass linting
- Respect the testing configuration of each package
- For shared components, test in their own package, not in consuming packages
## Implementation Checklist
When implementing tests, verify:
1. ✅ Tests focus on component/function behavior
2. ✅ Tests are isolated and don't depend on each other
3. ✅ Mocks are used appropriately and documented
4. ✅ Error states and edge cases are covered
5. ✅ Tests are readable and maintainable
6. ✅ Tests fail when they should (verify with temporary bug)
7. ✅ Test file structure matches source code structure
8. ✅ Import paths follow monorepo conventions
\ No newline at end of file
* @uniswap/web-admins
IPFS hash of the deployment: We are back with some new updates! Here’s the latest:
- CIDv0: `QmZhPHVskUQR1q2jr5D2AyMkfvkoBtbN95Z3ofUcwyH3Lh`
- CIDv1: `bafybeifiybibt7gtwvbhz4awqe7lzdd5m62cm7mcuriu5eva3zceuov4xy`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). Search Improvements: Enjoy an improved search algorithm, with more relevant information. Recent history also now includes wallet search results.
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://bafybeifiybibt7gtwvbhz4awqe7lzdd5m62cm7mcuriu5eva3zceuov4xy.ipfs.dweb.link/
- [ipfs://QmZhPHVskUQR1q2jr5D2AyMkfvkoBtbN95Z3ofUcwyH3Lh/](ipfs://QmZhPHVskUQR1q2jr5D2AyMkfvkoBtbN95Z3ofUcwyH3Lh/)
### 5.86.1 (2025-05-23)
### Bug Fixes
* **web:** hotfix prod cb 5792 patch (#20096) 11ba54c
Other changes:
- Improved backup discovery
- Improved token selector behavior, with more relevant network filtering, as well as an improved experience when flipping input and output.
- Improvements to NFT spam detection
- Various bug fixes and performance improvements
\ No newline at end of file
web/5.86.1 extension/1.21.0
\ No newline at end of file \ No newline at end of file
// Simple mock for expo-blur's BlurView
// This is needed to avoid the Storybook Web compilation error related to `expo-blur`, something like:
// Module parse failed: Unexpected token (29:12)
// node_modules/expo-blur/build/BlurView.web.js 29:12
// You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
// We don't actually need to use `expo-blur` in the Web App, as we just use CSS; so, we can mock it out.
export const BlurView = (props) => <div {...props} />;
export default BlurView;
...@@ -45,15 +45,7 @@ globalThis.matchMedia = ...@@ -45,15 +45,7 @@ globalThis.matchMedia =
require('react-native-reanimated').setUpTests() require('react-native-reanimated').setUpTests()
const MOCK_LANGUAGE = 'en-US' global.chrome = chrome
global.chrome = {
...chrome,
i18n: {
...global.chrome.i18n,
getUILanguage: jest.fn().mockReturnValue(MOCK_LANGUAGE)
}
}
jest.mock('src/app/navigation/utils', () => ({ jest.mock('src/app/navigation/utils', () => ({
useExtensionNavigation: () => ({ useExtensionNavigation: () => ({
...@@ -77,3 +69,4 @@ jest.mock('wallet/src/features/appearance/hooks', () => { ...@@ -77,3 +69,4 @@ jest.mock('wallet/src/features/appearance/hooks', () => {
useSelectedColorScheme: () => 'light', useSelectedColorScheme: () => 'light',
} }
}) })
import { useCallback, useEffect, useMemo } from 'react'
import { CopyButton } from 'src/app/components/buttons/CopyButton'
import { Flex, Text, useMedia } from 'ui/src'
import { logger } from 'utilities/src/logger/logger'
const ROW_SIZE = 3
export const MnemonicViewer = ({ mnemonic }: { mnemonic?: string[] }): JSX.Element => {
const media = useMedia()
const px = media.xxs ? '$spacing12' : '$spacing32'
const onCopyPress = useCallback(async () => {
if (!mnemonic) {
return
}
const mnemonicString = mnemonic.join(' ')
try {
if (mnemonicString) {
await navigator.clipboard.writeText(mnemonicString)
}
} catch (error) {
logger.error(error, {
tags: { file: 'MnemonicViewer.tsx', function: 'onCopyPress' },
})
}
}, [mnemonic])
useEffect(() => {
return () => {
navigator.clipboard.writeText('').catch((error) => {
logger.error(error, {
tags: { file: 'MnemonicViewer.tsx', function: 'MnemonicViewer#useEffect' },
})
})
}
}, [])
const rows = useMemo(() => {
if (!mnemonic) {
return null
}
const elements = []
for (let i = 0; i < mnemonic.length; i += ROW_SIZE) {
elements.push(<SeedPhraseRow key={i} startIndex={i + 1} words={mnemonic.slice(i, i + ROW_SIZE)} />)
}
return elements
}, [mnemonic])
return (
<Flex
backgroundColor="$surface2"
borderColor="$surface3"
borderRadius="$rounded20"
borderWidth="$spacing1"
gap="$spacing12"
position="relative"
px={px}
py="$spacing24"
width="100%"
>
{rows}
<Flex alignItems="center" left="50%" position="absolute" top={-16} transform="translateX(-50%)">
<CopyButton onCopyPress={onCopyPress} />
</Flex>
</Flex>
)
}
function SeedPhraseRow({ words, startIndex }: { words: string[]; startIndex: number }): JSX.Element {
return (
<Flex fill row>
{words.map((word, index) => (
<SeedPhraseWord key={index} index={index + startIndex} word={word} />
))}
</Flex>
)
}
function SeedPhraseWord({ index, word }: { index: number; word: string }): JSX.Element {
const media = useMedia()
const fontVariant = 'body3'
const gap = media.xxs ? '$spacing4' : '$spacing8'
return (
<Flex fill row flexBasis={0} gap={gap}>
<Text color="$neutral3" minWidth={16} variant={fontVariant}>
{index}
</Text>
<Text variant={fontVariant}>{word}</Text>
</Flex>
)
}
import { useSmartWalletNudges } from 'src/app/context/SmartWalletNudgesContext'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { PostSwapSmartWalletNudge } from 'wallet/src/components/smartWallet/modals/PostSwapSmartWalletNudge'
import { SmartWalletEnabledModal } from 'wallet/src/components/smartWallet/modals/SmartWalletEnabledModal'
export function SmartWalletNudgeModals(): JSX.Element | null {
const { activeModal, closeModal, openModal, dappInfo } = useSmartWalletNudges()
if (!activeModal) {
return null
}
switch (activeModal) {
case ModalName.PostSwapSmartWalletNudge:
return (
<PostSwapSmartWalletNudge
isOpen
onClose={closeModal}
dappInfo={dappInfo}
onEnableSmartWallet={() => openModal(ModalName.SmartWalletEnabledModal)}
/>
)
case ModalName.SmartWalletEnabledModal:
return <SmartWalletEnabledModal isOpen showReconnectDappPrompt={!!dappInfo} onClose={closeModal} />
default:
return null
}
}
import { ReactNode, createContext, useCallback, useContext, useState } from 'react'
import { ModalNameType } from 'uniswap/src/features/telemetry/constants'
type DappInfo = {
icon?: string
name?: string
}
type SmartWalletNudgesContextState = {
activeModal: ModalNameType | null
openModal: (modal: ModalNameType) => void
closeModal: () => void
dappInfo?: DappInfo
setDappInfo: (info?: DappInfo) => void
}
const SmartWalletNudgesContext = createContext<SmartWalletNudgesContextState | undefined>(undefined)
export function SmartWalletNudgesProvider({ children }: { children: ReactNode }): JSX.Element {
const [activeModal, setActiveModal] = useState<ModalNameType | null>(null)
const [dappInfo, setDappInfo] = useState<{
icon?: string
name?: string
}>()
const openModal = useCallback(
(modal: ModalNameType): void => {
setActiveModal(modal)
},
[setActiveModal],
)
const closeModal = useCallback((): void => {
setActiveModal(null)
}, [setActiveModal])
return (
<SmartWalletNudgesContext.Provider
value={{
activeModal,
openModal,
closeModal,
dappInfo,
setDappInfo,
}}
>
{children}
</SmartWalletNudgesContext.Provider>
)
}
export function useSmartWalletNudges(): SmartWalletNudgesContextState {
const context = useContext(SmartWalletNudgesContext)
if (!context) {
throw new Error('useSmartWalletNudges must be used within a SmartWalletNudgesProvider')
}
return context
}
...@@ -2,7 +2,6 @@ import { PropsWithChildren } from 'react' ...@@ -2,7 +2,6 @@ import { PropsWithChildren } from 'react'
import { I18nextProvider } from 'react-i18next' import { I18nextProvider } from 'react-i18next'
import { GraphqlProvider } from 'src/app/apollo' import { GraphqlProvider } from 'src/app/apollo'
import { TraceUserProperties } from 'src/app/components/Trace/TraceUserProperties' import { TraceUserProperties } from 'src/app/components/Trace/TraceUserProperties'
import { SmartWalletNudgesProvider } from 'src/app/context/SmartWalletNudgesContext'
import { ExtensionStatsigProvider } from 'src/app/core/StatsigProvider' import { ExtensionStatsigProvider } from 'src/app/core/StatsigProvider'
import { DatadogAppNameTag } from 'src/app/datadog' import { DatadogAppNameTag } from 'src/app/datadog'
import { getReduxStore } from 'src/store/store' import { getReduxStore } from 'src/store/store'
...@@ -25,12 +24,10 @@ export function BaseAppContainer({ ...@@ -25,12 +24,10 @@ export function BaseAppContainer({
<ErrorBoundary> <ErrorBoundary>
<GraphqlProvider> <GraphqlProvider>
<BlankUrlProvider> <BlankUrlProvider>
<SmartWalletNudgesProvider> <LocalizationContextProvider>
<LocalizationContextProvider> <TraceUserProperties />
<TraceUserProperties /> {children}
{children} </LocalizationContextProvider>
</LocalizationContextProvider>
</SmartWalletNudgesProvider>
</BlankUrlProvider> </BlankUrlProvider>
</GraphqlProvider> </GraphqlProvider>
</ErrorBoundary> </ErrorBoundary>
......
...@@ -5,7 +5,7 @@ import { initializeReduxStore } from 'src/store/store' ...@@ -5,7 +5,7 @@ import { initializeReduxStore } from 'src/store/store'
describe('OnboardingApp', () => { describe('OnboardingApp', () => {
// eslint-disable-next-line jest/expect-expect // eslint-disable-next-line jest/expect-expect
it('renders without error', async () => { it('renders without error', async () => {
initializeReduxStore() await initializeReduxStore()
render(<OnboardingApp />) render(<OnboardingApp />)
}) })
}) })
...@@ -21,7 +21,10 @@ import { ...@@ -21,7 +21,10 @@ import {
} from 'src/app/features/onboarding/OnboardingSteps' } from 'src/app/features/onboarding/OnboardingSteps'
import { OnboardingWrapper } from 'src/app/features/onboarding/OnboardingWrapper' import { OnboardingWrapper } from 'src/app/features/onboarding/OnboardingWrapper'
import { PasswordImport } from 'src/app/features/onboarding/PasswordImport' import { PasswordImport } from 'src/app/features/onboarding/PasswordImport'
import { NameWallet } from 'src/app/features/onboarding/create/NameWallet'
import { PasswordCreate } from 'src/app/features/onboarding/create/PasswordCreate' import { PasswordCreate } from 'src/app/features/onboarding/create/PasswordCreate'
import { TestMnemonic } from 'src/app/features/onboarding/create/TestMnemonic'
import { ViewMnemonic } from 'src/app/features/onboarding/create/ViewMnemonic'
import { ImportMnemonic } from 'src/app/features/onboarding/import/ImportMnemonic' import { ImportMnemonic } from 'src/app/features/onboarding/import/ImportMnemonic'
import { InitiatePasskeyAuth } from 'src/app/features/onboarding/import/InitiatePasskeyAuth' import { InitiatePasskeyAuth } from 'src/app/features/onboarding/import/InitiatePasskeyAuth'
import { PasskeyImport } from 'src/app/features/onboarding/import/PasskeyImport' import { PasskeyImport } from 'src/app/features/onboarding/import/PasskeyImport'
...@@ -39,10 +42,10 @@ import { setRouter, setRouterState } from 'src/app/navigation/state' ...@@ -39,10 +42,10 @@ import { setRouter, setRouterState } from 'src/app/navigation/state'
import { initExtensionAnalytics } from 'src/app/utils/analytics' import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { checksIfSupportsSidePanel } from 'src/app/utils/chrome' import { checksIfSupportsSidePanel } from 'src/app/utils/chrome'
import { PrimaryAppInstanceDebuggerLazy } from 'src/store/PrimaryAppInstanceDebuggerLazy' import { PrimaryAppInstanceDebuggerLazy } from 'src/store/PrimaryAppInstanceDebuggerLazy'
import { getReduxPersistor } from 'src/store/store'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension' import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension'
import { getReduxPersistor } from 'wallet/src/state/persistor'
const supportsSidePanel = checksIfSupportsSidePanel() const supportsSidePanel = checksIfSupportsSidePanel()
...@@ -65,9 +68,26 @@ const allRoutes = [ ...@@ -65,9 +68,26 @@ const allRoutes = [
element: ( element: (
<OnboardingStepsProvider <OnboardingStepsProvider
key={OnboardingRoutes.Create} key={OnboardingRoutes.Create}
steps={{
[CreateOnboardingSteps.Password]: <PasswordCreate />,
[CreateOnboardingSteps.ViewMnemonic]: <ViewMnemonic />,
[CreateOnboardingSteps.TestMnemonic]: <TestMnemonic />,
[CreateOnboardingSteps.Naming]: <NameWallet />,
[CreateOnboardingSteps.Complete]: <Complete flow={ExtensionOnboardingFlow.New} />,
}}
/>
),
},
{
path: OnboardingRoutes.Claim,
element: (
<OnboardingStepsProvider
key={OnboardingRoutes.Claim}
steps={{ steps={{
[CreateOnboardingSteps.ClaimUnitag]: <ClaimUnitagScreen />, [CreateOnboardingSteps.ClaimUnitag]: <ClaimUnitagScreen />,
[CreateOnboardingSteps.Password]: <PasswordCreate />, [CreateOnboardingSteps.Password]: <PasswordCreate />,
[CreateOnboardingSteps.ViewMnemonic]: <ViewMnemonic />,
[CreateOnboardingSteps.TestMnemonic]: <TestMnemonic />,
[CreateOnboardingSteps.Complete]: <Complete tryToClaimUnitag flow={ExtensionOnboardingFlow.New} />, [CreateOnboardingSteps.Complete]: <Complete tryToClaimUnitag flow={ExtensionOnboardingFlow.New} />,
}} }}
/> />
...@@ -93,6 +113,7 @@ const allRoutes = [ ...@@ -93,6 +113,7 @@ const allRoutes = [
steps={{ steps={{
[ImportPasskeySteps.InitiatePasskeyAuth]: <InitiatePasskeyAuth />, [ImportPasskeySteps.InitiatePasskeyAuth]: <InitiatePasskeyAuth />,
[ImportPasskeySteps.PasskeyImport]: <PasskeyImport />, [ImportPasskeySteps.PasskeyImport]: <PasskeyImport />,
// TODO(WALL-6383): modify this flow to ask user to verify their seed phrase.
[ImportOnboardingSteps.Password]: <PasswordImport flow={ExtensionOnboardingFlow.Passkey} />, [ImportOnboardingSteps.Password]: <PasswordImport flow={ExtensionOnboardingFlow.Passkey} />,
[ImportOnboardingSteps.Select]: <SelectWallets flow={ExtensionOnboardingFlow.Passkey} />, [ImportOnboardingSteps.Select]: <SelectWallets flow={ExtensionOnboardingFlow.Passkey} />,
[ImportOnboardingSteps.Complete]: <Complete flow={ExtensionOnboardingFlow.Passkey} />, [ImportOnboardingSteps.Complete]: <Complete flow={ExtensionOnboardingFlow.Passkey} />,
......
...@@ -3,15 +3,16 @@ import 'src/app/Global.css' ...@@ -3,15 +3,16 @@ import 'src/app/Global.css'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { RouterProvider, createHashRouter } from 'react-router-dom' import { RouterProvider, createHashRouter } from 'react-router-dom'
import { ErrorElement } from 'src/app/components/ErrorElement' import { ErrorElement } from 'src/app/components/ErrorElement'
import { BaseAppContainer } from 'src/app/core/BaseAppContainer' import { BaseAppContainer } from 'src/app/core/BaseAppContainer'
import { DatadogAppNameTag } from 'src/app/datadog' import { DatadogAppNameTag } from 'src/app/datadog'
import { initExtensionAnalytics } from 'src/app/utils/analytics' import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { Button, Flex, Image, Text } from 'ui/src' import { Button, Flex, Image, Text } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets' import { CHROME_LOGO, UNISWAP_LOGO } from 'ui/src/assets'
import { GoogleChromeLogo } from 'ui/src/components/logos/GoogleChromeLogo'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants' import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { ExtensionScreens } from 'uniswap/src/types/screens/extension'
...@@ -27,7 +28,11 @@ const router = createHashRouter([ ...@@ -27,7 +28,11 @@ const router = createHashRouter([
function PopupContent(): JSX.Element { function PopupContent(): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useDispatch()
useEffect(() => {
dispatch(syncAppWithDeviceLanguage())
}, [dispatch])
useTestnetModeForLoggingAndAnalytics() useTestnetModeForLoggingAndAnalytics()
const searchParams = new URLSearchParams(window.location.search) const searchParams = new URLSearchParams(window.location.search)
...@@ -53,7 +58,7 @@ function PopupContent(): JSX.Element { ...@@ -53,7 +58,7 @@ function PopupContent(): JSX.Element {
position="absolute" position="absolute"
right={-spacing.spacing4} right={-spacing.spacing4}
> >
<GoogleChromeLogo size={iconSizes.icon12} /> <Image height={iconSizes.icon12} source={CHROME_LOGO} width={iconSizes.icon12} />
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -22,7 +22,6 @@ import { RemoveRecoveryPhraseWallets } from 'src/app/features/settings/SettingsR ...@@ -22,7 +22,6 @@ import { RemoveRecoveryPhraseWallets } from 'src/app/features/settings/SettingsR
import { ViewRecoveryPhraseScreen } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/ViewRecoveryPhraseScreen' import { ViewRecoveryPhraseScreen } from 'src/app/features/settings/SettingsRecoveryPhraseScreen/ViewRecoveryPhraseScreen'
import { SettingsScreen } from 'src/app/features/settings/SettingsScreen' import { SettingsScreen } from 'src/app/features/settings/SettingsScreen'
import { SettingsScreenWrapper } from 'src/app/features/settings/SettingsScreenWrapper' import { SettingsScreenWrapper } from 'src/app/features/settings/SettingsScreenWrapper'
import { SmartWalletSettingsScreen } from 'src/app/features/settings/SmartWalletSettingsScreen'
import { SettingsChangePasswordScreen } from 'src/app/features/settings/password/SettingsChangePasswordScreen' import { SettingsChangePasswordScreen } from 'src/app/features/settings/password/SettingsChangePasswordScreen'
import { SwapFlowScreen } from 'src/app/features/swap/SwapFlowScreen' import { SwapFlowScreen } from 'src/app/features/swap/SwapFlowScreen'
import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked' import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked'
...@@ -37,7 +36,9 @@ import { ...@@ -37,7 +36,9 @@ import {
} from 'src/background/messagePassing/messageChannels' } from 'src/background/messagePassing/messageChannels'
import { BackgroundToSidePanelRequestType } from 'src/background/messagePassing/types/requests' import { BackgroundToSidePanelRequestType } from 'src/background/messagePassing/types/requests'
import { PrimaryAppInstanceDebuggerLazy } from 'src/store/PrimaryAppInstanceDebuggerLazy' import { PrimaryAppInstanceDebuggerLazy } from 'src/store/PrimaryAppInstanceDebuggerLazy'
import { getReduxPersistor } from 'src/store/store'
import { useResetUnitagsQueries } from 'uniswap/src/data/apiClients/unitagsApi/useResetUnitagsQueries' import { useResetUnitagsQueries } from 'uniswap/src/data/apiClients/unitagsApi/useResetUnitagsQueries'
import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { isDevEnv } from 'utilities/src/environment/env' import { isDevEnv } from 'utilities/src/environment/env'
...@@ -45,7 +46,6 @@ import { logger } from 'utilities/src/logger/logger' ...@@ -45,7 +46,6 @@ import { logger } from 'utilities/src/logger/logger'
import { ONE_SECOND_MS } from 'utilities/src/time/time' import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { useInterval } from 'utilities/src/time/timing' import { useInterval } from 'utilities/src/time/timing'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics' import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
import { getReduxPersistor } from 'wallet/src/state/persistor'
const router = createHashRouter([ const router = createHashRouter([
{ {
...@@ -104,10 +104,6 @@ const router = createHashRouter([ ...@@ -104,10 +104,6 @@ const router = createHashRouter([
path: SettingsRoutes.ManageConnections, path: SettingsRoutes.ManageConnections,
element: <SettingsManageConnectionsScreen />, element: <SettingsManageConnectionsScreen />,
}, },
{
path: SettingsRoutes.SmartWallet,
element: <SmartWalletSettingsScreen />,
},
], ],
}, },
{ {
...@@ -188,11 +184,16 @@ function useDappRequestPortListener(): void { ...@@ -188,11 +184,16 @@ function useDappRequestPortListener(): void {
} }
function SidebarWrapper(): JSX.Element { function SidebarWrapper(): JSX.Element {
const dispatch = useDispatch()
useDappRequestPortListener() useDappRequestPortListener()
useTestnetModeForLoggingAndAnalytics() useTestnetModeForLoggingAndAnalytics()
const resetUnitagsQueries = useResetUnitagsQueries() const resetUnitagsQueries = useResetUnitagsQueries()
useEffect(() => {
dispatch(syncAppWithDeviceLanguage())
}, [dispatch])
useEffect(() => { useEffect(() => {
return backgroundToSidePanelMessageChannel.addMessageListener( return backgroundToSidePanelMessageChannel.addMessageListener(
BackgroundToSidePanelRequestType.RefreshUnitags, BackgroundToSidePanelRequestType.RefreshUnitags,
......
import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext' import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext'
import { SwapDisplay } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/SwapDisplay' import { SwapDisplay } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/SwapDisplay'
import { ETH_ADDRESS } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants'
import { formatUnits, useSwapDetails } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/utils' import { formatUnits, useSwapDetails } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/utils'
import { UniswapXSwapRequest } from 'src/app/features/dappRequests/types/Permit2Types' import { UniswapXSwapRequest } from 'src/app/features/dappRequests/types/Permit2Types'
import { UniversalRouterCall } from 'src/app/features/dappRequests/types/UniversalRouterTypes' import { UniversalRouterCall } from 'src/app/features/dappRequests/types/UniversalRouterTypes'
import { DEFAULT_NATIVE_ADDRESS, DEFAULT_NATIVE_ADDRESS_LEGACY } from 'uniswap/src/features/chains/chainInfo' import { DEFAULT_NATIVE_ADDRESS } 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 { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
...@@ -115,7 +116,7 @@ export function UniswapXSwapRequestContent({ typedData }: { typedData: UniswapXS ...@@ -115,7 +116,7 @@ export function UniswapXSwapRequestContent({ typedData }: { typedData: UniswapXS
const { token: outputToken, startAmount: lastAmountOutParam } = typedData.message.witness.baseOutputs[0] const { token: outputToken, startAmount: lastAmountOutParam } = typedData.message.witness.baseOutputs[0]
const inputCurrencyInfo = useCurrencyInfo(buildCurrencyId(activeChain, inputToken)) const inputCurrencyInfo = useCurrencyInfo(buildCurrencyId(activeChain, inputToken))
const nativeEthOrOutputToken = outputToken === DEFAULT_NATIVE_ADDRESS ? DEFAULT_NATIVE_ADDRESS_LEGACY : outputToken const nativeEthOrOutputToken = outputToken === ETH_ADDRESS ? DEFAULT_NATIVE_ADDRESS : outputToken
const outputCurrencyInfo = useCurrencyInfo(buildCurrencyId(activeChain, nativeEthOrOutputToken)) const outputCurrencyInfo = useCurrencyInfo(buildCurrencyId(activeChain, nativeEthOrOutputToken))
assert( assert(
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
export const CONTRACT_BALANCE = BigNumber.from(2).pow(255) export const CONTRACT_BALANCE = BigNumber.from(2).pow(255)
export const ETH_ADDRESS = '0x0000000000000000000000000000000000000000'
export const MAX_UINT256 = BigNumber.from(2).pow(256).sub(1) export const MAX_UINT256 = BigNumber.from(2).pow(256).sub(1)
export const MAX_UINT160 = BigNumber.from(2).pow(160).sub(1) export const MAX_UINT160 = BigNumber.from(2).pow(160).sub(1)
...@@ -5,6 +5,7 @@ import { formatUnits as formatUnitsEthers } from 'ethers/lib/utils' ...@@ -5,6 +5,7 @@ import { formatUnits as formatUnitsEthers } from 'ethers/lib/utils'
import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { import {
CONTRACT_BALANCE, CONTRACT_BALANCE,
ETH_ADDRESS,
MAX_UINT160, MAX_UINT160,
MAX_UINT256, MAX_UINT256,
} from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants' } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants'
...@@ -29,7 +30,7 @@ import { ...@@ -29,7 +30,7 @@ import {
isUrCommandSweep, isUrCommandSweep,
isUrCommandUnwrapWeth, isUrCommandUnwrapWeth,
} from 'src/app/features/dappRequests/types/UniversalRouterTypes' } from 'src/app/features/dappRequests/types/UniversalRouterTypes'
import { DEFAULT_NATIVE_ADDRESS, DEFAULT_NATIVE_ADDRESS_LEGACY } from 'uniswap/src/features/chains/chainInfo' import { DEFAULT_NATIVE_ADDRESS } from 'uniswap/src/features/chains/chainInfo'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
import { assert } from 'utilities/src/errors' import { assert } from 'utilities/src/errors'
...@@ -69,10 +70,8 @@ export function useSwapDetails( ...@@ -69,10 +70,8 @@ export function useSwapDetails(
if (v4Command) { if (v4Command) {
// Extract details using the V4 helper // Extract details using the V4 helper
const v4Details = getTokenDetailsFromV4SwapCommands(v4Command, parsedCalldata.commands) const v4Details = getTokenDetailsFromV4SwapCommands(v4Command, parsedCalldata.commands)
inputAddress = inputAddress = v4Details.inputAddress === ETH_ADDRESS ? DEFAULT_NATIVE_ADDRESS : v4Details.inputAddress
v4Details.inputAddress === DEFAULT_NATIVE_ADDRESS ? DEFAULT_NATIVE_ADDRESS_LEGACY : v4Details.inputAddress outputAddress = v4Details.outputAddress === ETH_ADDRESS ? DEFAULT_NATIVE_ADDRESS : v4Details.outputAddress
outputAddress =
v4Details.outputAddress === DEFAULT_NATIVE_ADDRESS ? DEFAULT_NATIVE_ADDRESS_LEGACY : v4Details.outputAddress
inputValue = v4Details.inputValue || '0' inputValue = v4Details.inputValue || '0'
outputValue = v4Details.outputValue || '0' outputValue = v4Details.outputValue || '0'
} else { } else {
......
...@@ -11,6 +11,8 @@ import { EIP712Message, isEIP712TypedData } from 'src/app/features/dappRequests/ ...@@ -11,6 +11,8 @@ import { EIP712Message, isEIP712TypedData } from 'src/app/features/dappRequests/
import { isPermit2, isUniswapXSwapRequest } from 'src/app/features/dappRequests/types/Permit2Types' import { isPermit2, isUniswapXSwapRequest } from 'src/app/features/dappRequests/types/Permit2Types'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useHasAccountMismatchCallback } from 'uniswap/src/features/smartWallet/mismatch/hooks' import { useHasAccountMismatchCallback } from 'uniswap/src/features/smartWallet/mismatch/hooks'
import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking'
import { isAddress } from 'utilities/src/addresses' import { isAddress } from 'utilities/src/addresses'
...@@ -44,6 +46,7 @@ export function SignTypedDataRequestContent({ dappRequest }: SignTypedDataReques ...@@ -44,6 +46,7 @@ export function SignTypedDataRequestContent({ dappRequest }: SignTypedDataReques
function SignTypedDataRequestContentInner({ dappRequest }: SignTypedDataRequestProps): JSX.Element | null { function SignTypedDataRequestContentInner({ dappRequest }: SignTypedDataRequestProps): JSX.Element | null {
const { t } = useTranslation() const { t } = useTranslation()
const enablePermitMismatchUx = useFeatureFlag(FeatureFlags.EnablePermitMismatchUX)
const getHasMismatch = useHasAccountMismatchCallback() const getHasMismatch = useHasAccountMismatchCallback()
const parsedTypedData = JSON.parse(dappRequest.typedData) const parsedTypedData = JSON.parse(dappRequest.typedData)
...@@ -56,7 +59,7 @@ function SignTypedDataRequestContentInner({ dappRequest }: SignTypedDataRequestP ...@@ -56,7 +59,7 @@ function SignTypedDataRequestContentInner({ dappRequest }: SignTypedDataRequestP
const chainId = toSupportedChainId(domainChainId) const chainId = toSupportedChainId(domainChainId)
const hasMismatch = chainId ? getHasMismatch(chainId) : false const hasMismatch = chainId ? getHasMismatch(chainId) : false
if (hasMismatch) { if (enablePermitMismatchUx && hasMismatch) {
return <ActionCanNotBeCompletedContent /> return <ActionCanNotBeCompletedContent />
} }
......
...@@ -500,11 +500,7 @@ export function* handleGetCapabilities(request: GetCapabilitiesRequest, senderTa ...@@ -500,11 +500,7 @@ export function* handleGetCapabilities(request: GetCapabilitiesRequest, senderTa
// https://linear.app/uniswap/issue/WALL-6679/implement-getcapabilities-on-extensionwc-instead-of-hardcoded-values // https://linear.app/uniswap/issue/WALL-6679/implement-getcapabilities-on-extensionwc-instead-of-hardcoded-values
response: { response: {
[`0x${UniverseChainId.Sepolia.toString(16)}`]: { atomic: { status: 'supported' } }, [`0x${UniverseChainId.Sepolia.toString(16)}`]: { atomic: { status: 'supported' } },
[`0x${UniverseChainId.Mainnet.toString(16)}`]: { atomic: { status: 'supported' } },
[`0x${UniverseChainId.UnichainSepolia.toString(16)}`]: { atomic: { status: 'supported' } }, [`0x${UniverseChainId.UnichainSepolia.toString(16)}`]: { atomic: { status: 'supported' } },
[`0x${UniverseChainId.Unichain.toString(16)}`]: { atomic: { status: 'supported' } },
[`0x${UniverseChainId.Optimism.toString(16)}`]: { atomic: { status: 'supported' } },
[`0x${UniverseChainId.Base.toString(16)}`]: { atomic: { status: 'supported' } },
}, },
} }
yield* call(dappResponseMessageChannel.sendMessageToTab, senderTabInfo.id, response) yield* call(dappResponseMessageChannel.sendMessageToTab, senderTabInfo.id, response)
......
...@@ -83,7 +83,7 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }): ...@@ -83,7 +83,7 @@ const RotatingSettingsIcon = ({ onPressSettings }: { onPressSettings(): void }):
<TouchableArea <TouchableArea
hoverable hoverable
borderRadius="$roundedFull" borderRadius="$roundedFull"
p="$spacing6" p="$spacing8"
onHoverIn={onBegin} onHoverIn={onBegin}
onHoverOut={onCancel} onHoverOut={onCancel}
onPress={onPressSettingsLocal} onPress={onPressSettingsLocal}
...@@ -152,7 +152,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf ...@@ -152,7 +152,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf
return ( return (
<Flex gap="$spacing8"> <Flex gap="$spacing8">
<Flex row justifyContent="space-between" alignItems="flex-start"> <Flex row justifyContent="space-between">
<TouchableArea pressStyle={{ scale: 0.95 }} onPress={onPressAccount}> <TouchableArea pressStyle={{ scale: 0.95 }} onPress={onPressAccount}>
<Flex group row alignItems="center" gap="$spacing4"> <Flex group row alignItems="center" gap="$spacing4">
<Flex $group-hover={{ opacity: 0.6 }}> <Flex $group-hover={{ opacity: 0.6 }}>
...@@ -163,7 +163,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf ...@@ -163,7 +163,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf
</Flex> </Flex>
</Flex> </Flex>
</TouchableArea> </TouchableArea>
<Flex row alignItems="center" gap="$spacing6" justifyContent="space-around"> <Flex row alignItems="center" gap="$spacing4" justifyContent="space-around">
{showConnectionStatus && ( {showConnectionStatus && (
<Popover <Popover
offset={10} offset={10}
...@@ -177,7 +177,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf ...@@ -177,7 +177,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf
}} }}
> >
<Popover.Trigger onPress={toggleConnectPopup}> <Popover.Trigger onPress={toggleConnectPopup}>
<TouchableArea hoverable borderRadius="$roundedFull" p="$spacing6"> <TouchableArea hoverable borderRadius="$roundedFull" p="$spacing8">
<ConnectionStatusIcon <ConnectionStatusIcon
dappIconUrl={dappIconUrl} dappIconUrl={dappIconUrl}
dappUrl={dappUrl} dappUrl={dappUrl}
......
...@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react' ...@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { KeyboardKey } from 'src/app/features/onboarding/KeyboardKey' import { KeyboardKey } from 'src/app/features/onboarding/KeyboardKey'
import { MainContentWrapper } from 'src/app/features/onboarding/intro/MainContentWrapper' import { MainContentWrapper } from 'src/app/features/onboarding/intro/MainContentWrapper'
import { useFinishExtensionOnboarding } from 'src/app/features/onboarding/useFinishExtensionOnboarding'
import { useOpeningKeyboardShortCut } from 'src/app/hooks/useOpeningKeyboardShortCut' import { useOpeningKeyboardShortCut } from 'src/app/hooks/useOpeningKeyboardShortCut'
import { getCurrentTabAndWindowId } from 'src/app/navigation/utils' import { getCurrentTabAndWindowId } from 'src/app/navigation/utils'
import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels' import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels'
...@@ -16,7 +15,7 @@ import { iconSizes } from 'ui/src/theme' ...@@ -16,7 +15,7 @@ import { iconSizes } from 'ui/src/theme'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension' import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext' import { useFinishOnboarding, useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
export function Complete({ export function Complete({
flow, flow,
...@@ -44,11 +43,7 @@ export function Complete({ ...@@ -44,11 +43,7 @@ export function Complete({
}, [existingClaim, address, tryToClaimUnitag, unitagClaimAttempted, addUnitagClaim]) }, [existingClaim, address, tryToClaimUnitag, unitagClaimAttempted, addUnitagClaim])
// Activates onboarding accounts on component mount // Activates onboarding accounts on component mount
useFinishExtensionOnboarding({ useFinishOnboarding(terminateStoreSynchronization, flow, tryToClaimUnitag && !unitagClaimAttempted)
callback: terminateStoreSynchronization,
extensionOnboardingFlow: flow,
skip: tryToClaimUnitag && !unitagClaimAttempted,
})
useEffect(() => { useEffect(() => {
const onSidebarOpenedListener = onboardingMessageChannel.addMessageListener( const onSidebarOpenedListener = onboardingMessageChannel.addMessageListener(
......
...@@ -4,6 +4,9 @@ import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScr ...@@ -4,6 +4,9 @@ import { OnboardingScreenProps } from 'src/app/features/onboarding/OnboardingScr
export enum CreateOnboardingSteps { export enum CreateOnboardingSteps {
ClaimUnitag = 'claimUnitag', ClaimUnitag = 'claimUnitag',
Password = 'password', Password = 'password',
ViewMnemonic = 'mnemonic',
TestMnemonic = 'testMnemonic',
Naming = 'naming',
Complete = 'complete', Complete = 'complete',
} }
......
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { Outlet } from 'react-router-dom' import { Outlet } from 'react-router-dom'
import { DevMenuModal } from 'src/app/core/DevMenuModal' import { DevMenuModal } from 'src/app/core/DevMenuModal'
import { StorageWarningModal } from 'src/app/features/warnings/StorageWarningModal' import { StorageWarningModal } from 'src/app/features/warnings/StorageWarningModal'
...@@ -6,6 +7,7 @@ import { ONBOARDING_BACKGROUND_DARK, ONBOARDING_BACKGROUND_LIGHT } from 'src/ass ...@@ -6,6 +7,7 @@ import { ONBOARDING_BACKGROUND_DARK, ONBOARDING_BACKGROUND_LIGHT } from 'src/ass
import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels' import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels'
import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages' import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages'
import { Flex, Image, useIsDarkMode } from 'ui/src' import { Flex, Image, useIsDarkMode } from 'ui/src'
import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import { isProdEnv } from 'utilities/src/environment/env' import { isProdEnv } from 'utilities/src/environment/env'
import { OnboardingContextProvider } from 'wallet/src/features/onboarding/OnboardingContext' import { OnboardingContextProvider } from 'wallet/src/features/onboarding/OnboardingContext'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics' import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
...@@ -13,9 +15,14 @@ import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testne ...@@ -13,9 +15,14 @@ import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testne
export function OnboardingWrapper(): JSX.Element { export function OnboardingWrapper(): JSX.Element {
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const [isHighlighted, setIsHighlighted] = useState(false) const [isHighlighted, setIsHighlighted] = useState(false)
const dispatch = useDispatch()
useTestnetModeForLoggingAndAnalytics() useTestnetModeForLoggingAndAnalytics()
useEffect(() => {
dispatch(syncAppWithDeviceLanguage())
}, [dispatch])
useEffect(() => { useEffect(() => {
return onboardingMessageChannel.addMessageListener(OnboardingMessageType.HighlightOnboardingTab, (_message) => { return onboardingMessageChannel.addMessageListener(OnboardingMessageType.HighlightOnboardingTab, (_message) => {
// When the onboarding tab regains focus, we do a quick background change to bring attention to it. // When the onboarding tab regains focus, we do a quick background change to bring attention to it.
......
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Input } from 'src/app/components/Input'
import { saveDappConnection } from 'src/app/features/dapp/actions'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps'
import { Flex, Text, Unicon } from 'ui/src'
import { fonts, iconSizes } from 'ui/src/theme'
import { UNISWAP_WEB_URL } from 'uniswap/src/constants/urls'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension'
import { shortenAddress } from 'utilities/src/addresses'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
export function NameWallet(): JSX.Element {
const { t } = useTranslation()
const { getOnboardingAccount, setPendingWalletName } = useOnboardingContext()
const onboardingAccount = getOnboardingAccount()
const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const [walletName, setWalletName] = useState('')
const onboardingAccountAddress = onboardingAccount?.address
const onSubmit = async (): Promise<void> => {
if (walletName) {
setPendingWalletName(walletName)
}
if (onboardingAccount) {
await saveDappConnection(UNISWAP_WEB_URL, onboardingAccount)
}
goToNextStep()
}
return (
<Trace
logImpression
properties={{ flow: ExtensionOnboardingFlow.New }}
screen={ExtensionOnboardingScreens.NameWallet}
>
<OnboardingScreen
Icon={
onboardingAccountAddress ? <Unicon address={onboardingAccountAddress} size={iconSizes.icon64} /> : undefined
}
nextButtonEnabled={true}
nextButtonText={t('onboarding.name.wallet.button.text')}
subtitle={t('onboarding.name.wallet.subtitle')}
title={t('onboarding.name.wallet.title')}
onBack={goToPreviousStep}
onSubmit={onSubmit}
>
<Flex gap="$spacing24" py="$spacing24" width="100%">
<Input
autoFocus
large
backgroundColor="$surface1"
borderRadius="$rounded20"
fontSize={fonts.heading3.fontSize}
placeholder={onboardingAccount?.name}
py="$spacing32"
textAlign="center"
onChangeText={setWalletName}
onSubmitEditing={onSubmit}
/>
<Text color="$neutral3" textAlign="center" variant="subheading2">
{onboardingAccountAddress && shortenAddress(onboardingAccountAddress)}
</Text>
</Flex>
</OnboardingScreen>
</Trace>
)
}
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps'
import { RecoveryPhraseVerification } from 'src/app/features/recoveryPhraseVerification/RecoveryPhraseVerification'
import { NUMBER_OF_TESTS_FOR_RECOVERY_PHRASE_VERIFICATION } from 'src/app/features/settings/BackupRecoveryPhrase/constants'
import { Flex, Square, Text } from 'ui/src'
import { FileListCheck } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension'
import { useEvent } from 'utilities/src/react/hooks'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
import { BackupType } from 'wallet/src/features/wallet/accounts/types'
export function TestMnemonic({
numberOfTests = NUMBER_OF_TESTS_FOR_RECOVERY_PHRASE_VERIFICATION,
}: {
numberOfTests?: number
}): JSX.Element {
const { t } = useTranslation()
const { addBackupMethod, getOnboardingAccountAddress, getOnboardingAccountMnemonic } = useOnboardingContext()
const onboardingAccountAddress = getOnboardingAccountAddress()
const onboardingAccountMnemonic = getOnboardingAccountMnemonic()
const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
const onNext = useEvent((): void => {
if (!onboardingAccountMnemonic || !onboardingAccountAddress) {
return
}
goToNextStep()
})
const [subtitle, setSubtitle] = useState('')
const [numberOfWordsVerified, setNumberOfWordsVerified] = useState(0)
const [hasError, setHasError] = useState(false)
const onComplete = useEvent(() => {
addBackupMethod(BackupType.Manual)
goToNextStep()
})
return (
<Trace
logImpression
properties={{ flow: ExtensionOnboardingFlow.New }}
screen={ExtensionOnboardingScreens.ConfirmSeedPhrase}
>
<OnboardingScreen
Icon={
<Square alignSelf="center" backgroundColor="$surface2" borderRadius="$rounded12" size={iconSizes.icon48}>
<FileListCheck color="$neutral1" size="$icon.24" />
</Square>
}
nextButtonEnabled={false}
nextButtonText={t('onboarding.backup.manual.progress', {
completedStepsCount: numberOfWordsVerified,
totalStepsCount: numberOfTests,
})}
nextButtonVariant="default"
nextButtonEmphasis="secondary"
subtitle={subtitle}
title={t('onboarding.backup.manual.title')}
onBack={goToPreviousStep}
onSkip={onNext}
onSubmit={onNext}
>
<Flex fill gap="$spacing24" mb="$spacing24" width="100%">
{onboardingAccountMnemonic ? (
<RecoveryPhraseVerification
mnemonic={onboardingAccountMnemonic}
numberOfTests={numberOfTests}
onComplete={onComplete}
setHasError={setHasError}
setSubtitle={setSubtitle}
onWordVerified={(numberOfWordsVerified) => setNumberOfWordsVerified(numberOfWordsVerified)}
/>
) : null}
<Text color="$statusCritical" style={{ opacity: hasError ? 1 : 0 }} textAlign="center" variant="body3">
{t('onboarding.backup.manual.error')}
</Text>
</Flex>
</OnboardingScreen>
</Trace>
)
}
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MnemonicViewer } from 'src/app/components/MnemonicViewer'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps'
import { useSubmitOnEnter } from 'src/app/features/onboarding/utils'
import { BackupWarningBulletPoints } from 'src/app/features/settings/BackupRecoveryPhrase/BackupWarningBulletPoints'
import { TopLevelRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state'
import { Flex, LabeledCheckbox, Square, Text } from 'ui/src'
import { AlertTriangleFilled, FileListLock } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ExtensionOnboardingFlow, ExtensionOnboardingScreens } from 'uniswap/src/types/screens/extension'
import { logger } from 'utilities/src/logger/logger'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
enum ViewStep {
Info = 0,
View = 1,
}
export function ViewMnemonic(): JSX.Element {
const { t } = useTranslation()
const [viewStep, setViewStep] = useState<ViewStep>(ViewStep.Info)
const { goToNextStep } = useOnboardingSteps()
const [disclaimerChecked, setDisclaimerChecked] = useState(false)
const { getOnboardingAccountAddress, getOnboardingAccountMnemonic, retrieveOnboardingAccountMnemonic } =
useOnboardingContext()
const onboardingAccountAddress = getOnboardingAccountAddress()
const onboardingAccountMnemonic = getOnboardingAccountMnemonic()
useEffect(() => {
if (!onboardingAccountMnemonic) {
retrieveOnboardingAccountMnemonic().catch((e) => {
logger.error(e, {
tags: { file: 'ViewMnemonic', function: 'retrieveOnboardingAccountMnemonic' },
})
})
}
}, [onboardingAccountMnemonic, retrieveOnboardingAccountMnemonic])
// On Info step, next button should be enabled if mnemonic has been created.
// On View step, next button should be enabled if disclaimer is checked and mnemonic has been created.
const shouldEnableNextButton =
viewStep === ViewStep.View ? !!onboardingAccountAddress && disclaimerChecked : !!onboardingAccountAddress
const onSubmit = (): void => {
if (!shouldEnableNextButton) {
return
}
if (viewStep === ViewStep.Info) {
setViewStep(ViewStep.View)
return
}
if (onboardingAccountAddress && disclaimerChecked) {
goToNextStep()
}
}
useSubmitOnEnter(onSubmit)
return (
<Trace
logImpression
properties={{ flow: ExtensionOnboardingFlow.New }}
screen={ExtensionOnboardingScreens.ViewSeedPhrase}
>
<OnboardingScreen
Icon={
<Square
alignContent="center"
backgroundColor={viewStep === ViewStep.View ? '$surface2' : '$statusCritical2'}
borderRadius="$rounded12"
size={iconSizes.icon48}
>
{viewStep === ViewStep.View ? (
<FileListLock color="$neutral1" size="$icon.24" />
) : (
<AlertTriangleFilled color="$statusCritical" size="$icon.24" />
)}
</Square>
}
nextButtonEnabled={shouldEnableNextButton}
nextButtonText={t('common.button.continue')}
subtitle={
viewStep === ViewStep.View
? t('onboarding.backup.view.subtitle.message2')
: t('onboarding.backup.view.subtitle.message1')
}
title={t('onboarding.backup.view.title')}
onBack={(): void =>
navigate(`/${TopLevelRoutes.Onboarding}`, {
replace: true,
})
}
onSubmit={onSubmit}
>
{viewStep === ViewStep.Info ? (
<Flex my="$spacing24">
<BackupWarningBulletPoints />
</Flex>
) : (
<Flex gap="$spacing16" my="$spacing24" pt="$spacing8" width="100%">
<MnemonicViewer mnemonic={onboardingAccountMnemonic} />
<Flex backgroundColor="$surface2" borderRadius="$rounded16" p="$spacing12" overflow="hidden">
<LabeledCheckbox
checked={disclaimerChecked}
text={<Text variant="body3">{t('onboarding.backup.view.disclaimer')}</Text>}
onCheckPressed={(currentValue: boolean): void => setDisclaimerChecked(!currentValue)}
/>
</Flex>
</Flex>
)}
</OnboardingScreen>
</Trace>
)
}
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SelectWalletsSkeleton } from 'src/app/components/loading/SelectWalletSkeleton' import { SelectWalletsSkeleton } from 'src/app/components/loading/SelectWalletSkeleton'
import { saveDappConnection } from 'src/app/features/dapp/actions'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen' import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps' import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingSteps'
import { useSubmitOnEnter } from 'src/app/features/onboarding/utils' import { useSubmitOnEnter } from 'src/app/features/onboarding/utils'
import { Flex, ScrollView, SpinningLoader, Square, Text } from 'ui/src' import { Flex, ScrollView, SpinningLoader, Square, Text } from 'ui/src'
import { WalletFilled } from 'ui/src/components/icons' import { WalletFilled } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { UNISWAP_WEB_URL } from 'uniswap/src/constants/urls'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace' import 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 { useAsyncData, useEvent } from 'utilities/src/react/hooks' import { useAsyncData, useEvent } from 'utilities/src/react/hooks'
...@@ -18,6 +22,7 @@ import { BackupType } from 'wallet/src/features/wallet/accounts/types' ...@@ -18,6 +22,7 @@ import { BackupType } from 'wallet/src/features/wallet/accounts/types'
export function SelectWallets({ flow }: { flow: ExtensionOnboardingFlow }): JSX.Element { export function SelectWallets({ flow }: { flow: ExtensionOnboardingFlow }): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const shouldAutoConnect = useFeatureFlag(FeatureFlags.ExtensionAutoConnect)
const [buttonClicked, setButtonClicked] = useState(false) const [buttonClicked, setButtonClicked] = useState(false)
const { goToNextStep, goToPreviousStep } = useOnboardingSteps() const { goToNextStep, goToPreviousStep } = useOnboardingSteps()
...@@ -37,11 +42,17 @@ export function SelectWallets({ flow }: { flow: ExtensionOnboardingFlow }): JSX. ...@@ -37,11 +42,17 @@ export function SelectWallets({ flow }: { flow: ExtensionOnboardingFlow }): JSX.
} }
setButtonClicked(true) setButtonClicked(true)
await generateAccountsAndImportAddresses({ const importedAccounts = await generateAccountsAndImportAddresses({
selectedAddresses, selectedAddresses,
backupType: flow === ExtensionOnboardingFlow.Passkey ? BackupType.Passkey : BackupType.Manual, backupType: flow === ExtensionOnboardingFlow.Passkey ? BackupType.Passkey : BackupType.Manual,
}) })
// TODO(EXT-1375): figure out how to better auto connect existing wallets that may have connected via WC or some other method.
// Once that's solved the feature flag can be turned on/removed.
if (shouldAutoConnect && importedAccounts?.[0]) {
await saveDappConnection(UNISWAP_WEB_URL, importedAccounts[0])
}
goToNextStep() goToNextStep()
setButtonClicked(false) setButtonClicked(false)
}) })
......
...@@ -44,7 +44,7 @@ export function IntroScreen(): JSX.Element { ...@@ -44,7 +44,7 @@ export function IntroScreen(): JSX.Element {
<Flex row backgroundColor="$surface1" borderRadius="$rounded16"> <Flex row backgroundColor="$surface1" borderRadius="$rounded16">
<Button <Button
variant="branded" variant="branded"
onPress={(): void => navigate(`/${TopLevelRoutes.Onboarding}/${OnboardingRoutes.Create}`)} onPress={(): void => navigate(`/${TopLevelRoutes.Onboarding}/${OnboardingRoutes.Claim}`)}
> >
{isPasskeyImportEnabled {isPasskeyImportEnabled
? t('onboarding.landing.button.createAccount') ? t('onboarding.landing.button.createAccount')
......
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useFinishExtensionOnboarding } from 'src/app/features/onboarding/useFinishExtensionOnboarding'
import { terminateStoreSynchronization } from 'src/store/storeSynchronization' import { terminateStoreSynchronization } from 'src/store/storeSynchronization'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { Check, GraduationCap } from 'ui/src/components/icons' import { Check, GraduationCap } from 'ui/src/components/icons'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { useFinishOnboarding } from 'wallet/src/features/onboarding/OnboardingContext'
export function ResetComplete(): JSX.Element { export function ResetComplete(): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
// Activates onboarding accounts on component mount // Activates onboarding accounts on component mount
useFinishExtensionOnboarding({ callback: terminateStoreSynchronization }) useFinishOnboarding(terminateStoreSynchronization)
return ( return (
<> <>
......
import { useEffect, useRef } from 'react'
import { saveDappConnection } from 'src/app/features/dapp/actions'
import { UNISWAP_WEB_URL } from 'uniswap/src/constants/urls'
import { ImportType } from 'uniswap/src/types/onboarding'
import { ExtensionOnboardingFlow } from 'uniswap/src/types/screens/extension'
import { logger } from 'utilities/src/logger/logger'
import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingContext'
/**
* Activates onboarding accounts on component mount,
* and auto-connects to app.uniswap.org.
*/
export function useFinishExtensionOnboarding({
callback,
extensionOnboardingFlow,
skip,
}: {
callback?: () => void
extensionOnboardingFlow?: ExtensionOnboardingFlow
skip?: boolean
}): void {
const { finishOnboarding, getOnboardingOrImportedAccount, getOnboardingAccountAddress } = useOnboardingContext()
const importType = getImportType(getOnboardingAccountAddress(), extensionOnboardingFlow)
const isFinishedRef = useRef(false)
useEffect(() => {
if (skip || isFinishedRef.current) {
return
}
const runFinishOnboarding = async () => {
isFinishedRef.current = true
await finishOnboarding({ importType, extensionOnboardingFlow })
const account = getOnboardingOrImportedAccount()
if (account) {
await saveDappConnection(UNISWAP_WEB_URL, account)
}
callback?.()
}
runFinishOnboarding().catch((e) => {
logger.error(e, {
tags: { file: 'useFinishExtensionOnboarding.ts', function: 'finishOnboarding' },
})
})
}, [finishOnboarding, importType, callback, extensionOnboardingFlow, skip, getOnboardingOrImportedAccount])
}
function getImportType(
onboardingAccountAddress: string | undefined,
extensionOnboardingFlow: ExtensionOnboardingFlow | undefined,
): ImportType {
if (extensionOnboardingFlow === ExtensionOnboardingFlow.Passkey) {
return ImportType.Passkey
}
if (onboardingAccountAddress) {
return ImportType.CreateNew
}
return ImportType.RestoreMnemonic
}
...@@ -23,18 +23,13 @@ export function SettingsDropdown({ selected, items, disableDropdown, onSelect }: ...@@ -23,18 +23,13 @@ export function SettingsDropdown({ selected, items, disableDropdown, onSelect }:
return ( return (
<Flex> <Flex>
<Popover open={isOpen} stayInFrame={true} onOpenChange={setIsOpen}> <Popover open={isOpen} stayInFrame={true} onOpenChange={(open) => setIsOpen(open)}>
<Popover.Trigger disabled={disableDropdown}> <Popover.Trigger disabled={disableDropdown}>
<Flex row backgroundColor="$surface3" borderRadius="$roundedFull" cursor="pointer" p="$spacing8" gap="$gap4"> <Flex row backgroundColor="$surface3" borderRadius="$roundedFull" cursor="pointer" p="$spacing8" gap="$gap4">
<Text color="$neutral1" variant="buttonLabel4"> <Text color="$neutral1" variant="buttonLabel4">
{selected} {selected}
</Text> </Text>
<RotatableChevron <RotatableChevron color="$neutral1" direction="down" height={iconSizes.icon16} width={iconSizes.icon20} />
color="$neutral1"
direction={isOpen ? 'up' : 'down'}
height={iconSizes.icon16}
width={iconSizes.icon20}
/>
</Flex> </Flex>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content zIndex={zIndexes.popover} backgroundColor="$transparent" disableRemoveScroll={false}> <Popover.Content zIndex={zIndexes.popover} backgroundColor="$transparent" disableRemoveScroll={false}>
......
...@@ -48,6 +48,7 @@ import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' ...@@ -48,6 +48,7 @@ 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 { PasskeyManagementModal } from 'uniswap/src/features/passkey/PasskeyManagementModal' import { PasskeyManagementModal } from 'uniswap/src/features/passkey/PasskeyManagementModal'
import { setCurrentFiatCurrency, setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import { setCurrentFiatCurrency, setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice'
import { SmartWalletAdvancedSettingsModal } from 'uniswap/src/features/smartWallet/modals/SmartWalletAdvancedSettingsModal'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { WalletEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -65,7 +66,6 @@ import { authActions } from 'wallet/src/features/auth/saga' ...@@ -65,7 +66,6 @@ import { authActions } from 'wallet/src/features/auth/saga'
import { AuthActionType } from 'wallet/src/features/auth/types' import { AuthActionType } from 'wallet/src/features/auth/types'
import { selectHasViewedConnectionMigration } from 'wallet/src/features/behaviorHistory/selectors' import { selectHasViewedConnectionMigration } from 'wallet/src/features/behaviorHistory/selectors'
import { resetWalletBehaviorHistory, setHasViewedConnectionMigration } from 'wallet/src/features/behaviorHistory/slice' import { resetWalletBehaviorHistory, setHasViewedConnectionMigration } from 'wallet/src/features/behaviorHistory/slice'
import { SmartWalletAdvancedSettingsModal } from 'wallet/src/features/smartWallet/modals/SmartWalletAdvancedSettingsModal'
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'
import { useSignerAccounts } from 'wallet/src/features/wallet/hooks' import { useSignerAccounts } from 'wallet/src/features/wallet/hooks'
...@@ -125,11 +125,6 @@ export function SettingsScreen(): JSX.Element { ...@@ -125,11 +125,6 @@ export function SettingsScreen(): JSX.Element {
const handleAdvancedModalClose = useCallback(() => setIsAdvancedModalOpen(false), []) const handleAdvancedModalClose = useCallback(() => setIsAdvancedModalOpen(false), [])
const handleSmartWalletPress = useCallback(() => {
navigateTo(`${AppRoutes.Settings}/${SettingsRoutes.SmartWallet}`)
setIsAdvancedModalOpen(false)
}, [navigateTo])
useEffect(() => { useEffect(() => {
getIsDefaultProviderFromStorage() getIsDefaultProviderFromStorage()
.then((newIsDefaultProvider) => setIsDefaultProvider(newIsDefaultProvider)) .then((newIsDefaultProvider) => setIsDefaultProvider(newIsDefaultProvider))
...@@ -151,18 +146,12 @@ export function SettingsScreen(): JSX.Element { ...@@ -151,18 +146,12 @@ export function SettingsScreen(): JSX.Element {
return ( return (
<Trace logImpression screen={ExtensionScreens.Settings}> <Trace logImpression screen={ExtensionScreens.Settings}>
{isLanguageModalOpen ? ( {isLanguageModalOpen ? <SettingsLanguageModal onClose={() => setIsLanguageModalOpen(false)} /> : undefined}
<SettingsLanguageModal isOpen={isLanguageModalOpen} onClose={() => setIsLanguageModalOpen(false)} />
) : undefined}
{isPortfolioBalanceModalOpen ? ( {isPortfolioBalanceModalOpen ? (
<PortfolioBalanceModal <PortfolioBalanceModal onClose={() => setIsPortfolioBalanceModalOpen(false)} />
isOpen={isPortfolioBalanceModalOpen}
onClose={() => setIsPortfolioBalanceModalOpen(false)}
/>
) : undefined} ) : undefined}
{isPermissionsModalOpen ? ( {isPermissionsModalOpen ? (
<PermissionsModal <PermissionsModal
isOpen={isPermissionsModalOpen}
handleDefaultBrowserToggle={handleDefaultBrowserToggle} handleDefaultBrowserToggle={handleDefaultBrowserToggle}
isDefaultBrowserProvider={isDefaultProvider} isDefaultBrowserProvider={isDefaultProvider}
onClose={() => setIsPermissionsModalOpen(false)} onClose={() => setIsPermissionsModalOpen(false)}
...@@ -174,7 +163,6 @@ export function SettingsScreen(): JSX.Element { ...@@ -174,7 +163,6 @@ export function SettingsScreen(): JSX.Element {
onTestnetModeToggled={handleTestnetModeToggle} onTestnetModeToggled={handleTestnetModeToggle}
isOpen={isAdvancedModalOpen} isOpen={isAdvancedModalOpen}
onClose={handleAdvancedModalClose} onClose={handleAdvancedModalClose}
onPressSmartWallet={handleSmartWalletPress}
/> />
{hasPasskeyBackup && ( {hasPasskeyBackup && (
<PasskeyManagementModal <PasskeyManagementModal
......
import { useTranslation } from 'react-i18next'
import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { Flex } from 'ui/src'
import {
SmartWalletHelpIcon,
SmartWalletSettingsContent,
} from 'wallet/src/features/smartWallet/SmartWalletSettingsContent'
export function SmartWalletSettingsScreen(): JSX.Element {
const { t } = useTranslation()
return (
<Flex fill gap="$gap16">
<ScreenHeader
title={t('settings.setting.smartWallet.action.smartWallet')}
rightColumn={<SmartWalletHelpIcon />}
/>
<SmartWalletSettingsContent />
</Flex>
)
}
import { useCallback, useState } from 'react' import { useState } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { useSmartWalletNudges } from 'src/app/context/SmartWalletNudgesContext'
import { useExtensionNavigation } from 'src/app/navigation/utils' import { useExtensionNavigation } from 'src/app/navigation/utils'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
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 { ModalName } from 'uniswap/src/features/telemetry/constants'
import { selectFilteredChainIds } from 'uniswap/src/features/transactions/swap/contexts/selectors' import { selectFilteredChainIds } from 'uniswap/src/features/transactions/swap/contexts/selectors'
import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/form/hooks/useSwapPrefilledState' import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/form/hooks/useSwapPrefilledState'
import { prepareSwapFormState } from 'uniswap/src/features/transactions/types/transactionState' import { prepareSwapFormState } from 'uniswap/src/features/transactions/types/transactionState'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow' import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow'
import { useActiveAccountWithThrow, useHasSmartWalletConsent } from 'wallet/src/features/wallet/hooks' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks'
export function SwapFlowScreen(): JSX.Element { export function SwapFlowScreen(): JSX.Element {
const { navigateBack, locationState } = useExtensionNavigation() const { navigateBack, locationState } = useExtensionNavigation()
...@@ -34,25 +32,9 @@ export function SwapFlowScreen(): JSX.Element { ...@@ -34,25 +32,9 @@ export function SwapFlowScreen(): JSX.Element {
const swapPrefilledState = useSwapPrefilledState(initialTransactionState) const swapPrefilledState = useSwapPrefilledState(initialTransactionState)
const { openModal, setDappInfo } = useSmartWalletNudges()
const hasSmartWalletConsent = useHasSmartWalletConsent()
const onSubmitSwap = useCallback(async () => {
// TODO(WALL-6765): check if wallet is already delegated
if (hasSmartWalletConsent === false) {
openModal(ModalName.PostSwapSmartWalletNudge)
setDappInfo(undefined)
}
}, [openModal, hasSmartWalletConsent, setDappInfo])
return ( return (
<Flex fill p="$spacing12"> <Flex fill p="$spacing12">
<WalletSwapFlow <WalletSwapFlow prefilledState={swapPrefilledState} walletNeedsRestore={false} onClose={navigateBack} />
prefilledState={swapPrefilledState}
walletNeedsRestore={false}
onClose={navigateBack}
onSubmitSwap={onSubmitSwap}
/>
</Flex> </Flex>
) )
} }
...@@ -6,6 +6,7 @@ export enum TopLevelRoutes { ...@@ -6,6 +6,7 @@ export enum TopLevelRoutes {
} }
export enum OnboardingRoutes { export enum OnboardingRoutes {
Claim = 'claim',
Create = 'create', Create = 'create',
Import = 'import', Import = 'import',
ImportPasskey = 'import-passkey', ImportPasskey = 'import-passkey',
...@@ -42,7 +43,6 @@ export enum SettingsRoutes { ...@@ -42,7 +43,6 @@ export enum SettingsRoutes {
BackupRecoveryPhrase = 'backup-recovery-phrase', BackupRecoveryPhrase = 'backup-recovery-phrase',
RemoveRecoveryPhrase = 'remove-recovery-phrase', RemoveRecoveryPhrase = 'remove-recovery-phrase',
ManageConnections = 'manage-connections', ManageConnections = 'manage-connections',
SmartWallet = 'smart-wallet',
} }
export enum RemoveRecoveryPhraseRoutes { export enum RemoveRecoveryPhraseRoutes {
......
import { useCallback, useMemo, useRef } from 'react' import { useCallback, 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 { DappRequestQueue } from 'src/app/features/dappRequests/DappRequestQueue' import { DappRequestQueue } from 'src/app/features/dappRequests/DappRequestQueue'
import { ForceUpgradeModal } from 'src/app/features/forceUpgrade/ForceUpgradeModal' import { ForceUpgradeModal } from 'src/app/features/forceUpgrade/ForceUpgradeModal'
import { HomeScreen } from 'src/app/features/home/HomeScreen' import { HomeScreen } from 'src/app/features/home/HomeScreen'
...@@ -220,8 +219,6 @@ function LoggedIn(): JSX.Element { ...@@ -220,8 +219,6 @@ function LoggedIn(): JSX.Element {
{isChromeWindowFocused && <TransactionHistoryUpdater />} {isChromeWindowFocused && <TransactionHistoryUpdater />}
<DappRequestQueue /> <DappRequestQueue />
<SmartWalletNudgeModals />
</> </>
) )
} }
......
...@@ -2,9 +2,9 @@ import { initDappStore } from 'src/app/features/dapp/saga' ...@@ -2,9 +2,9 @@ import { initDappStore } from 'src/app/features/dapp/saga'
import { dappRequestApprovalWatcher } from 'src/app/features/dappRequests/dappRequestApprovalWatcherSaga' import { dappRequestApprovalWatcher } from 'src/app/features/dappRequests/dappRequestApprovalWatcherSaga'
import { dappRequestWatcher } from 'src/app/features/dappRequests/saga' import { dappRequestWatcher } from 'src/app/features/dappRequests/saga'
import { call, spawn } from 'typed-redux-saga' import { call, spawn } from 'typed-redux-saga'
import { appLanguageWatcherSaga } from 'uniswap/src/features/language/saga'
import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient' import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient'
import { authActions, authReducer, authSaga, authSagaName } from 'wallet/src/features/auth/saga' import { authActions, authReducer, authSaga, authSagaName } from 'wallet/src/features/auth/saga'
import { deviceLocaleWatcher } from 'wallet/src/features/i18n/deviceLocaleWatcherSaga'
import { initProviders } from 'wallet/src/features/providers/saga' import { initProviders } from 'wallet/src/features/providers/saga'
import { swapActions, swapReducer, swapSaga, swapSagaName } from 'wallet/src/features/transactions/swap/swapSaga' import { swapActions, swapReducer, swapSaga, swapSagaName } from 'wallet/src/features/transactions/swap/swapSaga'
import { import {
...@@ -13,8 +13,7 @@ import { ...@@ -13,8 +13,7 @@ import {
tokenWrapSaga, tokenWrapSaga,
tokenWrapSagaName, tokenWrapSagaName,
} from 'wallet/src/features/transactions/swap/wrapSaga' } from 'wallet/src/features/transactions/swap/wrapSaga'
import { watchTransactionEvents } from 'wallet/src/features/transactions/watcher/transactionFinalizationSaga' import { transactionWatcher, watchTransactionEvents } from 'wallet/src/features/transactions/transactionWatcherSaga'
import { transactionWatcher } from 'wallet/src/features/transactions/watcher/transactionWatcherSaga'
import { import {
editAccountActions, editAccountActions,
editAccountReducer, editAccountReducer,
...@@ -64,12 +63,12 @@ export const monitoredSagas: Record<string, MonitoredSaga> = { ...@@ -64,12 +63,12 @@ export const monitoredSagas: Record<string, MonitoredSaga> = {
} as const } as const
const sagasInitializedOnStartup = [ const sagasInitializedOnStartup = [
appLanguageWatcherSaga,
initDappStore, initDappStore,
dappRequestApprovalWatcher, dappRequestApprovalWatcher,
dappRequestWatcher, dappRequestWatcher,
initProviders, initProviders,
watchTransactionEvents, watchTransactionEvents,
deviceLocaleWatcher,
] as const ] as const
export const monitoredSagaReducers = getMonitoredSagaReducers(monitoredSagas) export const monitoredSagaReducers = getMonitoredSagaReducers(monitoredSagas)
......
import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector' import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector'
import { STATE_STORAGE_KEY } from 'src/store/constants' import { STATE_STORAGE_KEY } from 'src/store/constants'
import { ExtensionState } from 'src/store/extensionReducer' import { ExtensionState } from 'src/store/extensionReducer'
import { readDeprecatedReduxedChromeStorage } from 'src/store/reduxedChromeStorageToReduxPersistMigration'
export async function readReduxStateFromStorage(storageChanges?: { export async function readReduxStateFromStorage(storageChanges?: {
[key: string]: chrome.storage.StorageChange [key: string]: chrome.storage.StorageChange
...@@ -24,6 +25,15 @@ export async function readReduxStateFromStorage(storageChanges?: { ...@@ -24,6 +25,15 @@ export async function readReduxStateFromStorage(storageChanges?: {
} }
export async function readIsOnboardedFromStorage(): Promise<boolean> { export async function readIsOnboardedFromStorage(): Promise<boolean> {
const state = await readReduxStateFromStorage() // The migration will happen in the sidebar, not in the background script,
// because the background script never persists the state (only reads it).
// So we need to check both the old and new storage keys to avoid the onboarding
// flow re-opening the first time the migration needs to run.
const [oldReduxedChromeStorageState, newReduxPersistState] = await Promise.all([
readDeprecatedReduxedChromeStorage(),
readReduxStateFromStorage(),
])
const state = oldReduxedChromeStorageState ?? newReduxPersistState
return state ? isOnboardedSelector(state) : false return state ? isOnboardedSelector(state) : false
} }
...@@ -10,5 +10,9 @@ ...@@ -10,5 +10,9 @@
*/ */
setTimeout(() => { setTimeout(() => {
import('src/entry/sidebar') const script = document.createElement('script')
script.type = 'text/javascript'
script.async = true
script.src = './sidebar.js'
document.body.appendChild(script)
}, 10) }, 10)
...@@ -4,16 +4,19 @@ ...@@ -4,16 +4,19 @@
import React from 'react' import React from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import OnboardingApp from 'src/app/core/OnboardingApp' import OnboardingApp from 'src/app/core/OnboardingApp'
import { initializeReduxStore } from 'src/store/store'
import { ExtensionAppLocation, StoreSynchronization } from 'src/store/storeSynchronization' import { ExtensionAppLocation, StoreSynchronization } from 'src/store/storeSynchronization'
import { logger } from 'utilities/src/logger/logger'
;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any ;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any
// The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem // The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem
// see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326 // see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326
function initOnboarding() { async function initOnboarding(): Promise<void> {
await initializeReduxStore()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = document.getElementById('onboarding-root')! const container = document.getElementById('onboarding-root')!
const root = createRoot(container) const root = createRoot(container)
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<OnboardingApp /> <OnboardingApp />
...@@ -21,6 +24,20 @@ function initOnboarding() { ...@@ -21,6 +24,20 @@ function initOnboarding() {
) )
} }
StoreSynchronization.init(ExtensionAppLocation.Tab) StoreSynchronization.init(ExtensionAppLocation.Tab).catch((error) => {
logger.error(error, {
tags: {
file: 'onboarding.ts',
function: 'initPrimaryInstanceHandler',
},
})
})
initOnboarding() initOnboarding().catch((error) => {
logger.error(error, {
tags: {
file: 'onboarding.ts',
function: 'initOnboarding',
},
})
})
...@@ -5,11 +5,14 @@ import { StrictMode } from 'react' ...@@ -5,11 +5,14 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import PopupApp from 'src/app/core/PopupApp' import PopupApp from 'src/app/core/PopupApp'
import { initializeReduxStore } from 'src/store/store' import { initializeReduxStore } from 'src/store/store'
import { logger } from 'utilities/src/logger/logger'
;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any ;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any
// The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem // The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem
// see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326 // see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326
function initPopup() { async function initPopup(): Promise<void> {
await initializeReduxStore({ readOnly: true })
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = document.getElementById('popup-root')! const container = document.getElementById('popup-root')!
const root = createRoot(container) const root = createRoot(container)
...@@ -21,6 +24,11 @@ function initPopup() { ...@@ -21,6 +24,11 @@ function initPopup() {
) )
} }
initializeReduxStore({ readOnly: true }) initPopup().catch((error) => {
logger.error(error, {
initPopup() tags: {
file: 'popup.tsx',
function: 'initPopup',
},
})
})
...@@ -9,6 +9,7 @@ import { createRoot } from 'react-dom/client' ...@@ -9,6 +9,7 @@ import { createRoot } from 'react-dom/client'
import SidebarApp from 'src/app/core/SidebarApp' import SidebarApp from 'src/app/core/SidebarApp'
import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels' import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels'
import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages' import { OnboardingMessageType } from 'src/background/messagePassing/types/ExtensionMessages'
import { initializeReduxStore } from 'src/store/store'
import { ExtensionAppLocation, StoreSynchronization } from 'src/store/storeSynchronization' import { ExtensionAppLocation, StoreSynchronization } from 'src/store/storeSynchronization'
import { initializeScrollWatcher } from 'uniswap/src/components/modals/ScrollLock' import { initializeScrollWatcher } from 'uniswap/src/components/modals/ScrollLock'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -16,24 +17,15 @@ import { logger } from 'utilities/src/logger/logger' ...@@ -16,24 +17,15 @@ import { logger } from 'utilities/src/logger/logger'
// The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem // The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem
// see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326 // see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326
function initSidebar(): void { async function initSidebar(): Promise<void> {
onboardingMessageChannel await initializeReduxStore()
.sendMessage({ await onboardingMessageChannel.sendMessage({
type: OnboardingMessageType.SidebarOpened, type: OnboardingMessageType.SidebarOpened,
}) })
.catch((error) => {
logger.error(error, {
tags: {
file: 'sidebar.ts',
function: 'onboardingMessageChannel.sendMessage',
},
})
})
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = window.document.querySelector('#root')! const container = window.document.querySelector('#root')!
const root = createRoot(container) const root = createRoot(container)
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<SidebarApp /> <SidebarApp />
...@@ -41,8 +33,22 @@ function initSidebar(): void { ...@@ -41,8 +33,22 @@ function initSidebar(): void {
) )
} }
StoreSynchronization.init(ExtensionAppLocation.SidePanel) StoreSynchronization.init(ExtensionAppLocation.SidePanel).catch((error) => {
logger.error(error, {
initSidebar() tags: {
file: 'sidebar.ts',
function: 'initPrimaryInstanceHandler',
},
})
})
initSidebar().catch((error) => {
logger.error(error, {
tags: {
file: 'sidebar.ts',
function: 'initSidebar',
},
})
})
initializeScrollWatcher() initializeScrollWatcher()
...@@ -5,11 +5,14 @@ import { StrictMode } from 'react' ...@@ -5,11 +5,14 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import UnitagClaimApp from 'src/app/core/UnitagClaimApp' import UnitagClaimApp from 'src/app/core/UnitagClaimApp'
import { initializeReduxStore } from 'src/store/store' import { initializeReduxStore } from 'src/store/store'
import { logger } from 'utilities/src/logger/logger'
;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any ;(globalThis as any).regeneratorRuntime = undefined // eslint-disable-line @typescript-eslint/no-explicit-any
// The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem // The globalThis.regeneratorRuntime = undefined addresses a potentially unsafe-eval problem
// see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326 // see https://github.com/facebook/regenerator/issues/378#issuecomment-802628326
function initUnitagClaim(): void { async function initUnitagClaim(): Promise<void> {
await initializeReduxStore({ readOnly: true })
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const container = document.getElementById('unitag-claim-root')! const container = document.getElementById('unitag-claim-root')!
const root = createRoot(container) const root = createRoot(container)
...@@ -21,6 +24,11 @@ function initUnitagClaim(): void { ...@@ -21,6 +24,11 @@ function initUnitagClaim(): void {
) )
} }
initializeReduxStore({ readOnly: true }) initUnitagClaim().catch((error) => {
logger.error(error, {
initUnitagClaim() tags: {
file: 'unitagClaim.tsx',
function: 'initUnitagClaim',
},
})
})
...@@ -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.22.0", "version": "1.21.0",
"minimum_chrome_version": "116", "minimum_chrome_version": "116",
"icons": { "icons": {
"16": "assets/icon16.png", "16": "assets/icon16.png",
......
import { ExtensionState } from 'src/store/extensionReducer'
// TODO(EXT-1028): remove this file once the migration is no longer needed.
const REDUXED_STORAGE_KEY = 'reduxed'
// These functions are used to migrate the redux state persistence from `reduxed-chrome-storage` to `redux-persist`.
// The actual migration happens when the sidebar initializes the redux store. See `initializeReduxStore` in `store.ts`.
export async function readDeprecatedReduxedChromeStorage(): Promise<ExtensionState | undefined> {
const reduxedArray = (await chrome.storage.local.get(REDUXED_STORAGE_KEY))?.[REDUXED_STORAGE_KEY]
if (!reduxedArray) {
return undefined
}
// The `reduxed` storage is an array: [id, timestamp, state]
const [, , state] = reduxedArray
if (!state) {
return undefined
}
return state as ExtensionState
}
export async function deleteDeprecatedReduxedChromeStorage(): Promise<void> {
await chrome.storage.local.remove(REDUXED_STORAGE_KEY)
}
import { PreloadedState } from 'redux'
import { persistReducer, persistStore } from 'redux-persist' import { persistReducer, persistStore } from 'redux-persist'
import { localStorage } from 'redux-persist-webextension-storage' import { localStorage } from 'redux-persist-webextension-storage'
import { rootExtensionSaga } from 'src/app/saga' import { rootExtensionSaga } from 'src/app/saga'
...@@ -6,13 +7,15 @@ import { PERSIST_KEY } from 'src/store/constants' ...@@ -6,13 +7,15 @@ import { PERSIST_KEY } from 'src/store/constants'
import { enhancePersistReducer } from 'src/store/enhancePersistReducer' import { enhancePersistReducer } from 'src/store/enhancePersistReducer'
import { ExtensionState, extensionPersistedStateList, extensionReducer } from 'src/store/extensionReducer' import { ExtensionState, extensionPersistedStateList, extensionReducer } from 'src/store/extensionReducer'
import { EXTENSION_STATE_VERSION, migrations } from 'src/store/migrations' import { EXTENSION_STATE_VERSION, migrations } from 'src/store/migrations'
import {
deleteDeprecatedReduxedChromeStorage,
readDeprecatedReduxedChromeStorage,
} from 'src/store/reduxedChromeStorageToReduxPersistMigration'
import { fiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api' import { fiatOnRampAggregatorApi } from 'uniswap/src/features/fiatOnRamp/api'
import { delegationListenerMiddleware } from 'uniswap/src/features/smartWallet/delegation/slice' import { delegationListenerMiddleware } from 'uniswap/src/features/smartWallet/delegation/slice'
import { createDatadogReduxEnhancer } from 'utilities/src/logger/datadog/Datadog' import { createDatadogReduxEnhancer } from 'utilities/src/logger/datadog/Datadog'
import { logger } from 'utilities/src/logger/logger'
import { createStore } from 'wallet/src/state' import { createStore } from 'wallet/src/state'
import { createMigrate } from 'wallet/src/state/createMigrate' import { createMigrate } from 'wallet/src/state/createMigrate'
import { setReduxPersistor } from 'wallet/src/state/persistor'
const persistConfig = { const persistConfig = {
key: PERSIST_KEY, key: PERSIST_KEY,
...@@ -31,9 +34,10 @@ const dataDogReduxEnhancer = createDatadogReduxEnhancer({ ...@@ -31,9 +34,10 @@ const dataDogReduxEnhancer = createDatadogReduxEnhancer({
}, },
}) })
const setupStore = (): ReturnType<typeof createStore> => { const setupStore = (preloadedState?: PreloadedState<ExtensionState>): ReturnType<typeof createStore> => {
return createStore({ return createStore({
reducer: persistedReducer, reducer: persistedReducer,
preloadedState,
additionalSagas: [rootExtensionSaga], additionalSagas: [rootExtensionSaga],
middlewareBefore: __DEV__ ? [loggerMiddleware] : [], middlewareBefore: __DEV__ ? [loggerMiddleware] : [],
middlewareAfter: [fiatOnRampAggregatorApi.middleware, delegationListenerMiddleware.middleware], middlewareAfter: [fiatOnRampAggregatorApi.middleware, delegationListenerMiddleware.middleware],
...@@ -42,29 +46,32 @@ const setupStore = (): ReturnType<typeof createStore> => { ...@@ -42,29 +46,32 @@ const setupStore = (): ReturnType<typeof createStore> => {
} }
let store: ReturnType<typeof setupStore> | undefined let store: ReturnType<typeof setupStore> | undefined
let persistor: ReturnType<typeof persistStore> | undefined
export function initializeReduxStore(args?: { readOnly?: boolean }): void { export async function initializeReduxStore(args?: { readOnly?: boolean }): Promise<{
if (store) { store: ReturnType<typeof setupStore>
// This should never happen. It's only here to alert us if a bug is introduced in the future. persistor: ReturnType<typeof persistStore>
logger.error(new Error('`initializeReduxStore` called when already initialized'), { }> {
tags: { // Migrate the old `reduxed-chrome-storage` persisted state to `redux-persist`.
file: 'store.ts', // TODO(EXT-985): we might need to pass the old store through `createMigrations` when we implement migrations.
function: 'initializeReduxStore', const oldStore = await readDeprecatedReduxedChromeStorage()
},
})
return store = setupStore(oldStore)
} persistor = persistStore(store)
store = setupStore()
const persistor = persistStore(store)
setReduxPersistor(persistor)
if (args?.readOnly) { if (args?.readOnly) {
// This means the store will be initialized with the persisted state from disk, but it won't persist any changes. // This means the store will be initialized with the persisted state from disk, but it won't persist any changes.
// Only useful for use cases where we don't want to modify the state (for example, a popup window instead of the sidebar). // Only useful for use cases where we don't want to modify the state (for example, a popup window instead of the sidebar).
persistor.pause() persistor.pause()
} }
// We wait a few seconds to make sure the store is fully initialized and persisted before deleting the old storage.
// This is needed because otherwise the background script might think the user is not onboarded if it reads the storage while it's being migrated.
if (oldStore) {
setTimeout(deleteDeprecatedReduxedChromeStorage, 5000)
}
return { store, persistor }
} }
export function getReduxStore(): ReturnType<typeof setupStore> { export function getReduxStore(): ReturnType<typeof setupStore> {
...@@ -74,4 +81,11 @@ export function getReduxStore(): ReturnType<typeof setupStore> { ...@@ -74,4 +81,11 @@ export function getReduxStore(): ReturnType<typeof setupStore> {
return store return store
} }
export function getReduxPersistor(): ReturnType<typeof persistStore> {
if (!persistor) {
throw new Error('Invalid call to `getReduxPersistor` before store has been initialized')
}
return persistor
}
export type AppStore = ReturnType<typeof setupStore> export type AppStore = ReturnType<typeof setupStore>
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { initializeReduxStore } from 'src/store/store' import { getReduxPersistor, initializeReduxStore } from 'src/store/store'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { getReduxPersistor } from 'wallet/src/state/persistor'
import { PersistedStorage } from 'wallet/src/utils/persistedStorage' import { PersistedStorage } from 'wallet/src/utils/persistedStorage'
/** /**
...@@ -33,7 +32,7 @@ export enum ExtensionAppLocation { ...@@ -33,7 +32,7 @@ export enum ExtensionAppLocation {
Tab = 1, Tab = 1,
} }
function initPrimaryInstanceHandler(appLocation: ExtensionAppLocation): void { async function initPrimaryInstanceHandler(appLocation: ExtensionAppLocation): Promise<void> {
if (isInitialized) { if (isInitialized) {
// This is just to prevent bugs being introduced in the future. // This is just to prevent bugs being introduced in the future.
logger.error(new Error('`initPrimaryInstanceHandler` called when already initialized'), { logger.error(new Error('`initPrimaryInstanceHandler` called when already initialized'), {
...@@ -45,7 +44,7 @@ function initPrimaryInstanceHandler(appLocation: ExtensionAppLocation): void { ...@@ -45,7 +44,7 @@ function initPrimaryInstanceHandler(appLocation: ExtensionAppLocation): void {
return return
} }
initializeReduxStore() await initializeReduxStore()
const onStorageChangedListener: Parameters<typeof chrome.storage.onChanged.addListener>[0] = async ( const onStorageChangedListener: Parameters<typeof chrome.storage.onChanged.addListener>[0] = async (
changes, changes,
...@@ -104,11 +103,7 @@ function initPrimaryInstanceHandler(appLocation: ExtensionAppLocation): void { ...@@ -104,11 +103,7 @@ function initPrimaryInstanceHandler(appLocation: ExtensionAppLocation): void {
window.addEventListener('focus', onWindowFocusListener) window.addEventListener('focus', onWindowFocusListener)
// We always set the current app instance as the primary when it first launches. // We always set the current app instance as the primary when it first launches.
sessionStorage.setItem(PRIMARY_APP_INSTANCE_ID_KEY, currentAppInstanceId).catch((error) => { await sessionStorage.setItem(PRIMARY_APP_INSTANCE_ID_KEY, currentAppInstanceId)
logger.error(error, {
tags: { file: 'storeSynchronization.ts', function: 'sessionStorage.setItem' },
})
})
// This will be used in the onboarding flow when the user completes onboarding but the tab remains open. // This will be used in the onboarding flow when the user completes onboarding but the tab remains open.
// We don't want this tab to become the primary ever again when it's focused. // We don't want this tab to become the primary ever again when it's focused.
......
...@@ -111,14 +111,13 @@ const { ...@@ -111,14 +111,13 @@ const {
} }
: {}, : {},
compress: false, compress: false,
hot: true, // Enable HMR
static: { static: {
directory: path.join(__dirname, '../dev'), directory: path.join(__dirname, '../dev'),
}, },
client: { client: {
// logging: "info", // logging: "info",
progress: true, progress: true,
reconnect: true, reconnect: false,
overlay: { overlay: {
errors: true, errors: true,
warnings: false, warnings: false,
...@@ -282,7 +281,6 @@ module.exports = (env) => { ...@@ -282,7 +281,6 @@ module.exports = (env) => {
'react-native-vector-icons$': 'react-native-vector-icons/dist', 'react-native-vector-icons$': 'react-native-vector-icons/dist',
src: path.resolve(__dirname, 'src'), // absolute imports in apps/web src: path.resolve(__dirname, 'src'), // absolute imports in apps/web
'react-native-gesture-handler$': require.resolve('react-native-gesture-handler'), 'react-native-gesture-handler$': require.resolve('react-native-gesture-handler'),
'expo-blur': require.resolve('./__mocks__/expo-blur.js'),
}, },
// Add support for web-based extensions so we can share code between mobile/extension // Add support for web-based extensions so we can share code between mobile/extension
extensions: [ extensions: [
......
flows: flows:
- 'flows/onboarding/*' - 'flows/onboarding/*'
- 'flows/swap/*' - 'flows/swap/*'
- 'flows/restore/*'
baselineBranch: main baselineBranch: main
executionOrder: executionOrder:
continueOnFailure: true continueOnFailure: true
appId: com.uniswap.mobile.dev
---
- runFlow: ../../shared-flows/start.yaml
- runFlow: ../../shared-flows/recover-fast.yaml
- tapOn:
id: 'account-header-settings-icon'
- waitForAnimationToEnd
- swipe:
direction: 'up'
- waitForAnimationToEnd
- tapOn:
id: 'app-settings-dev-modal'
- waitForAnimationToEnd
- tapOn:
id: 'seed-phrase-private-keys-accordion'
- tapOn:
id: 'delete-seed-phrase-button'
- tapOn: 'Delete'
- tapOn:
id: 'delete-private-keys-button'
- tapOn: 'Delete'
- killApp
- launchApp
- waitForAnimationToEnd
- assertVisible:
text: 'Recover your wallet'
- assertVisible:
id: 'continue'
- assertNotVisible:
id: 'cancel'
- tapOn:
id: 'continue'
- waitForAnimationToEnd
- back
- waitForAnimationToEnd
- extendedWaitUntil:
visible:
text: 'No backups found'
timeout: 5000 # wait for cloud backup to fail
- inputText: ${E2E_RECOVERY_PHRASE}
- tapOn:
id: 'continue'
- waitForAnimationToEnd
- assertVisible:
id: 'account-header-avatar'
...@@ -4,10 +4,6 @@ appId: com.uniswap.mobile.dev ...@@ -4,10 +4,6 @@ appId: com.uniswap.mobile.dev
env: env:
E2E_RECOVERY_PHRASE: ${E2E_RECOVERY_PHRASE} E2E_RECOVERY_PHRASE: ${E2E_RECOVERY_PHRASE}
--- ---
- extendedWaitUntil:
visible:
id: ${output.testIds.ImportAccount}
timeout: 30000 # Starting the app during local dev loads the JS bundle which can take much longer than a normal build
- tapOn: - tapOn:
id: ${output.testIds.ImportAccount} id: ${output.testIds.ImportAccount}
- waitForAnimationToEnd - waitForAnimationToEnd
......
...@@ -71,9 +71,9 @@ if (isCI && datadogPropertiesAvailable) { ...@@ -71,9 +71,9 @@ if (isCI && datadogPropertiesAvailable) {
apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle" apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle"
} }
def devVersionName = "1.52" def devVersionName = "1.51"
def betaVersionName = "1.52" def betaVersionName = "1.51"
def prodVersionName = "1.52" def prodVersionName = "1.51"
android { android {
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
......
...@@ -3060,7 +3060,7 @@ ...@@ -3060,7 +3060,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3113,7 +3113,7 @@ ...@@ -3113,7 +3113,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
...@@ -3166,7 +3166,7 @@ ...@@ -3166,7 +3166,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
...@@ -3219,7 +3219,7 @@ ...@@ -3219,7 +3219,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
...@@ -3257,7 +3257,7 @@ ...@@ -3257,7 +3257,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3293,7 +3293,7 @@ ...@@ -3293,7 +3293,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
...@@ -3328,7 +3328,7 @@ ...@@ -3328,7 +3328,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
...@@ -3363,7 +3363,7 @@ ...@@ -3363,7 +3363,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
...@@ -3410,7 +3410,7 @@ ...@@ -3410,7 +3410,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3456,7 +3456,7 @@ ...@@ -3456,7 +3456,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
...@@ -3502,7 +3502,7 @@ ...@@ -3502,7 +3502,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
...@@ -3548,7 +3548,7 @@ ...@@ -3548,7 +3548,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
...@@ -3590,7 +3590,7 @@ ...@@ -3590,7 +3590,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3633,7 +3633,7 @@ ...@@ -3633,7 +3633,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
...@@ -3676,7 +3676,7 @@ ...@@ -3676,7 +3676,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
...@@ -3719,7 +3719,7 @@ ...@@ -3719,7 +3719,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
...@@ -3755,7 +3755,7 @@ ...@@ -3755,7 +3755,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3793,7 +3793,7 @@ ...@@ -3793,7 +3793,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3995,7 +3995,7 @@ ...@@ -3995,7 +3995,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -4040,7 +4040,7 @@ ...@@ -4040,7 +4040,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
...@@ -4151,7 +4151,7 @@ ...@@ -4151,7 +4151,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -4223,7 +4223,7 @@ ...@@ -4223,7 +4223,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
...@@ -4334,7 +4334,7 @@ ...@@ -4334,7 +4334,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -4406,7 +4406,7 @@ ...@@ -4406,7 +4406,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.52; MARKETING_VERSION = 1.51;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
...@@ -22,7 +22,7 @@ import { AppModals } from 'src/app/modals/AppModals' ...@@ -22,7 +22,7 @@ import { AppModals } from 'src/app/modals/AppModals'
import { NavigationContainer } from 'src/app/navigation/NavigationContainer' import { NavigationContainer } from 'src/app/navigation/NavigationContainer'
import { useIsPartOfNavigationTree } from 'src/app/navigation/hooks' import { useIsPartOfNavigationTree } from 'src/app/navigation/hooks'
import { AppStackNavigator } from 'src/app/navigation/navigation' import { AppStackNavigator } from 'src/app/navigation/navigation'
import { store } from 'src/app/store' import { persistor, store } from 'src/app/store'
import { TraceUserProperties } from 'src/components/Trace/TraceUserProperties' import { TraceUserProperties } from 'src/components/Trace/TraceUserProperties'
import { OfflineBanner } from 'src/components/banners/OfflineBanner' import { OfflineBanner } from 'src/components/banners/OfflineBanner'
import { initAppsFlyer } from 'src/features/analytics/appsflyer' import { initAppsFlyer } from 'src/features/analytics/appsflyer'
...@@ -66,6 +66,7 @@ import { StatsigUser, Storage, getStatsigClient } from 'uniswap/src/features/gat ...@@ -66,6 +66,7 @@ import { StatsigUser, Storage, getStatsigClient } from 'uniswap/src/features/gat
import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext' import { LocalizationContextProvider } from 'uniswap/src/features/language/LocalizationContext'
import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks'
import { clearNotificationQueue } from 'uniswap/src/features/notifications/slice' import { clearNotificationQueue } from 'uniswap/src/features/notifications/slice'
import { syncAppWithDeviceLanguage } from 'uniswap/src/features/settings/slice'
import Trace from 'uniswap/src/features/telemetry/Trace' import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
...@@ -81,7 +82,6 @@ import { isIOS } from 'utilities/src/platform' ...@@ -81,7 +82,6 @@ import { isIOS } from 'utilities/src/platform'
import { useAsyncData } from 'utilities/src/react/hooks' import { useAsyncData } from 'utilities/src/react/hooks'
import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext' import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext'
import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary'
import { getReduxPersistor } from 'wallet/src/state/persistor'
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient' import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient'
import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks' import { useCurrentAppearanceSetting } from 'wallet/src/features/appearance/hooks'
...@@ -259,7 +259,7 @@ function AppOuter(): JSX.Element | null { ...@@ -259,7 +259,7 @@ function AppOuter(): JSX.Element | null {
return ( return (
<ApolloProvider client={client}> <ApolloProvider client={client}>
<PersistGate loading={null} persistor={getReduxPersistor()}> <PersistGate loading={null} persistor={persistor}>
<ErrorBoundary> <ErrorBoundary>
<BlankUrlProvider> <BlankUrlProvider>
<LocalizationContextProvider> <LocalizationContextProvider>
...@@ -315,6 +315,7 @@ function AppInner(): JSX.Element { ...@@ -315,6 +315,7 @@ function AppInner(): JSX.Element {
useEffect(() => { useEffect(() => {
dispatch(clearNotificationQueue()) // clear all in-app toasts on app start dispatch(clearNotificationQueue()) // clear all in-app toasts on app start
dispatch(syncAppWithDeviceLanguage())
}, [dispatch]) }, [dispatch])
useEffect(() => { useEffect(() => {
......
...@@ -6,8 +6,6 @@ import { navigate } from 'src/app/navigation/rootNavigation' ...@@ -6,8 +6,6 @@ import { navigate } from 'src/app/navigation/rootNavigation'
import { AccountList } from 'src/components/accounts/AccountList' import { AccountList } from 'src/components/accounts/AccountList'
import { checkCloudBackupOrShowAlert } from 'src/components/mnemonic/cloudImportUtils' import { checkCloudBackupOrShowAlert } from 'src/components/mnemonic/cloudImportUtils'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal' import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { WalletRestoreType } from 'src/components/RestoreWalletModal/RestoreWalletModalState'
import { useWalletRestore } from 'src/features/wallet/useWalletRestore'
import { Button, Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Button, Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
...@@ -57,7 +55,6 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -57,7 +55,6 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
const dispatch = useDispatch() const dispatch = useDispatch()
const hasImportedSeedPhrase = useNativeAccountExists() const hasImportedSeedPhrase = useNativeAccountExists()
const isModalOpen = useIsFocused() const isModalOpen = useIsFocused()
const { openWalletRestoreModal, walletRestoreType } = useWalletRestore()
const sortedMnemonicAccounts = useSelector(selectSortedSignerMnemonicAccounts) const sortedMnemonicAccounts = useSelector(selectSortedSignerMnemonicAccounts)
...@@ -119,12 +116,6 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -119,12 +116,6 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
const onPressCreateNewWallet = async (): Promise<void> => { const onPressCreateNewWallet = async (): Promise<void> => {
setShowAddWalletModal(false) setShowAddWalletModal(false)
onClose() onClose()
if (walletRestoreType === WalletRestoreType.SeedPhrase) {
openWalletRestoreModal()
return
}
if (hasImportedSeedPhrase) { if (hasImportedSeedPhrase) {
await createAdditionalAccount() await createAdditionalAccount()
} else { } else {
...@@ -225,16 +216,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -225,16 +216,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
} }
return options return options
}, [ }, [activeAccountAddress, dispatch, hasImportedSeedPhrase, onClose, sortedMnemonicAccounts, t])
activeAccountAddress,
dispatch,
hasImportedSeedPhrase,
onClose,
sortedMnemonicAccounts,
t,
openWalletRestoreModal,
walletRestoreType,
])
const accountsWithoutActive = accounts.filter((a) => a.address !== activeAccountAddress) const accountsWithoutActive = accounts.filter((a) => a.address !== activeAccountAddress)
......
import React from 'react' import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'
import { LazyModalRenderer } from 'src/app/modals/LazyModalRenderer' import { LazyModalRenderer } from 'src/app/modals/LazyModalRenderer'
import { SendTokenModal } from 'src/app/modals/SendTokenModal' import { SendTokenModal } from 'src/app/modals/SendTokenModal'
import { SwapModal } from 'src/app/modals/SwapModal' import { SwapModal } from 'src/app/modals/SwapModal'
import { WalletConnectModals } from 'src/components/Requests/WalletConnectModals' import { WalletConnectModals } from 'src/components/Requests/WalletConnectModals'
import { SettingsAppearanceModal } from 'src/components/Settings/SettingsAppearanceModal'
import { ForceUpgradeModal } from 'src/components/forceUpgrade/ForceUpgradeModal' import { ForceUpgradeModal } from 'src/components/forceUpgrade/ForceUpgradeModal'
import { FiatOnRampAggregatorModal } from 'src/features/fiatOnRamp/FiatOnRampAggregatorModal' import { FiatOnRampAggregatorModal } from 'src/features/fiatOnRamp/FiatOnRampAggregatorModal'
import { LockScreenModal } from 'src/features/lockScreen/LockScreenModal' import { LockScreenModal } from 'src/features/lockScreen/LockScreenModal'
import { closeModal } from 'src/features/modals/modalSlice'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { SettingsLanguageModal } from 'wallet/src/components/settings/language/SettingsLanguageModal'
import { PermissionsModal } from 'wallet/src/components/settings/permissions/PermissionsModal'
import { PortfolioBalanceModal } from 'wallet/src/components/settings/portfolioBalance/PortfolioBalanceModal'
import { QueuedOrderModal } from 'wallet/src/features/transactions/swap/modals/QueuedOrderModal' import { QueuedOrderModal } from 'wallet/src/features/transactions/swap/modals/QueuedOrderModal'
/** /**
...@@ -20,6 +26,20 @@ import { QueuedOrderModal } from 'wallet/src/features/transactions/swap/modals/Q ...@@ -20,6 +26,20 @@ import { QueuedOrderModal } from 'wallet/src/features/transactions/swap/modals/Q
*/ */
export function AppModals(): JSX.Element { export function AppModals(): JSX.Element {
const dispatch = useDispatch()
const onCloseLanguageModal = useCallback(() => {
dispatch(closeModal({ name: ModalName.LanguageSelector }))
}, [dispatch])
const onClosePortfolioBalanceModal = useCallback(() => {
dispatch(closeModal({ name: ModalName.PortfolioBalanceModal }))
}, [dispatch])
const onClosePermissionsModal = useCallback(() => {
dispatch(closeModal({ name: ModalName.PermissionsModal }))
}, [dispatch])
return ( return (
<> <>
<LazyModalRenderer name={ModalName.FiatOnRampAggregator}> <LazyModalRenderer name={ModalName.FiatOnRampAggregator}>
...@@ -41,6 +61,22 @@ export function AppModals(): JSX.Element { ...@@ -41,6 +61,22 @@ export function AppModals(): JSX.Element {
<WalletConnectModals /> <WalletConnectModals />
<QueuedOrderModal /> <QueuedOrderModal />
<LazyModalRenderer name={ModalName.LanguageSelector}>
<SettingsLanguageModal onClose={onCloseLanguageModal} />
</LazyModalRenderer>
<LazyModalRenderer name={ModalName.PortfolioBalanceModal}>
<PortfolioBalanceModal onClose={onClosePortfolioBalanceModal} />
</LazyModalRenderer>
<LazyModalRenderer name={ModalName.PermissionsModal}>
<PermissionsModal onClose={onClosePermissionsModal} />
</LazyModalRenderer>
<LazyModalRenderer name={ModalName.SettingsAppearance}>
<SettingsAppearanceModal />
</LazyModalRenderer>
</> </>
) )
} }
import { DdRum } from '@datadog/mobile-react-native' import { DdRum } from '@datadog/mobile-react-native'
import React, { useCallback, useEffect } from 'react' import React, { useCallback, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation'
import { BiometricsIconProps, useBiometricsIcon } from 'src/components/icons/useBiometricsIcon' import { BiometricsIconProps, useBiometricsIcon } from 'src/components/icons/useBiometricsIcon'
import { WalletRestoreType } from 'src/components/RestoreWalletModal/RestoreWalletModalState'
import { useBiometricAppSettings } from 'src/features/biometrics/useBiometricAppSettings' import { useBiometricAppSettings } from 'src/features/biometrics/useBiometricAppSettings'
import { useOsBiometricAuthEnabled } from 'src/features/biometrics/useOsBiometricAuthEnabled' import { useOsBiometricAuthEnabled } from 'src/features/biometrics/useOsBiometricAuthEnabled'
import { useBiometricPrompt } from 'src/features/biometricsSettings/hooks' import { useBiometricPrompt } from 'src/features/biometricsSettings/hooks'
...@@ -11,26 +9,18 @@ import { closeModal } from 'src/features/modals/modalSlice' ...@@ -11,26 +9,18 @@ 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 { useHapticFeedback } from 'src/utils/haptics/useHapticFeedback'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { 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'
import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow' import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow'
import { useActiveAccount, useHasSmartWalletConsent } from 'wallet/src/features/wallet/hooks'
import { setSmartWalletConsent } from 'wallet/src/features/wallet/slice'
/* Need to track the swap modal manually until it's integrated in to react-navigation */ /* Need to track the swap modal manually until it's integrated in to react-navigation */
const DATADOG_VIEW_KEY = 'global-swap-modal' const DATADOG_VIEW_KEY = 'global-swap-modal'
export function SwapModal(): JSX.Element { export function SwapModal(): JSX.Element {
const appDispatch = useDispatch() const appDispatch = useDispatch()
const eip5792MethodsEnabled = useFeatureFlag(FeatureFlags.Eip5792Methods)
const { initialState } = useSelector(selectModalState(ModalName.Swap)) const { initialState } = useSelector(selectModalState(ModalName.Swap))
const { hapticFeedback } = useHapticFeedback() const { hapticFeedback } = useHapticFeedback()
const address = useActiveAccount()?.address
const hasSmartWalletConsent = useHasSmartWalletConsent()
const onClose = useCallback((): void => { const onClose = useCallback((): void => {
appDispatch(closeModal({ name: ModalName.Swap })) appDispatch(closeModal({ name: ModalName.Swap }))
...@@ -44,7 +34,7 @@ export function SwapModal(): JSX.Element { ...@@ -44,7 +34,7 @@ export function SwapModal(): JSX.Element {
appDispatch(updateSwapStartTimestamp({ timestamp })) appDispatch(updateSwapStartTimestamp({ timestamp }))
}, [appDispatch]) }, [appDispatch])
const { openWalletRestoreModal, walletRestoreType } = useWalletRestore() const { openWalletRestoreModal, walletNeedsRestore } = useWalletRestore()
const swapPrefilledState = useSwapPrefilledState(initialState) const swapPrefilledState = useSwapPrefilledState(initialState)
...@@ -58,26 +48,8 @@ export function SwapModal(): JSX.Element { ...@@ -58,26 +48,8 @@ export function SwapModal(): JSX.Element {
authTrigger={requiresBiometrics ? biometricsTrigger : undefined} authTrigger={requiresBiometrics ? biometricsTrigger : undefined}
openWalletRestoreModal={openWalletRestoreModal} openWalletRestoreModal={openWalletRestoreModal}
prefilledState={swapPrefilledState} prefilledState={swapPrefilledState}
walletNeedsRestore={walletRestoreType === WalletRestoreType.NewDevice} walletNeedsRestore={Boolean(walletNeedsRestore)}
onSubmitSwap={async () => { onSubmitSwap={hapticFeedback.success}
await hapticFeedback.success()
if (!eip5792MethodsEnabled) {
return
}
// TODO(WALL-6765): check if wallet is already delegated
if (address && hasSmartWalletConsent === false) {
navigate(ModalName.PostSwapSmartWalletNudge, {
onEnableSmartWallet: () => {
appDispatch(setSmartWalletConsent({ address, smartWalletConsent: true }))
navigate(ModalName.SmartWalletEnabledModal, {
showReconnectDappPrompt: false,
})
},
})
}
}}
onClose={onClose} onClose={onClose}
/> />
) )
......
...@@ -150,33 +150,18 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -150,33 +150,18 @@ exports[`AccountSwitcher renders correctly 1`] = `
</View> </View>
</View> </View>
<View <View
collapsable={false}
focusVisibleStyle={{}}
forwardedRef={[Function]}
hitSlop={16} hitSlop={16}
jestAnimatedStyle={
{
"value": {},
}
}
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]} onResponderGrant={[Function]}
onResponderMove={[Function]} onResponderMove={[Function]}
onResponderRelease={[Function]} onResponderRelease={[Function]}
onResponderTerminate={[Function]} onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]} onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]} onStartShouldSetResponder={[Function]}
role="button"
style={ style={
{ {
"backgroundColor": "transparent",
"borderBottomLeftRadius": 12,
"borderBottomRightRadius": 12,
"borderTopLeftRadius": 12,
"borderTopRightRadius": 12,
"flexDirection": "column", "flexDirection": "column",
"opacity": 1, "opacity": 1,
"transform": [ "transform": [
...@@ -189,8 +174,6 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -189,8 +174,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
testID="copy" testID="copy"
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
...@@ -204,12 +187,6 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -204,12 +187,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
"borderBottomRightRadius": 999999, "borderBottomRightRadius": 999999,
"borderTopLeftRadius": 999999, "borderTopLeftRadius": 999999,
"borderTopRightRadius": 999999, "borderTopRightRadius": 999999,
"color": {
"dynamic": {
"dark": "#FFFFFF",
"light": "#222222",
},
},
"flexDirection": "row", "flexDirection": "row",
"gap": 4, "gap": 4,
"justifyContent": "center", "justifyContent": "center",
...@@ -224,8 +201,6 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -224,8 +201,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
<Text <Text
allowFontScaling={true} allowFontScaling={true}
maxFontSizeMultiplier={1.4} maxFontSizeMultiplier={1.4}
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"color": { "color": {
...@@ -464,32 +439,25 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -464,32 +439,25 @@ exports[`AccountSwitcher renders correctly 1`] = `
</View> </View>
</View> </View>
<View <View
collapsable={false} hitSlop={
focusVisibleStyle={{}}
forwardedRef={[Function]}
jestAnimatedStyle={
{ {
"value": {}, "bottom": 5,
"left": 5,
"right": 5,
"top": 5,
} }
} }
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]} onResponderGrant={[Function]}
onResponderMove={[Function]} onResponderMove={[Function]}
onResponderRelease={[Function]} onResponderRelease={[Function]}
onResponderTerminate={[Function]} onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]} onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]} onStartShouldSetResponder={[Function]}
role="button"
style={ style={
{ {
"backgroundColor": "transparent",
"borderBottomLeftRadius": 12,
"borderBottomRightRadius": 12,
"borderTopLeftRadius": 12,
"borderTopRightRadius": 12,
"flexDirection": "column", "flexDirection": "column",
"marginTop": 16, "marginTop": 16,
"opacity": 1, "opacity": 1,
...@@ -502,17 +470,9 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -502,17 +470,9 @@ exports[`AccountSwitcher renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
"color": {
"dynamic": {
"dark": "#FFFFFF",
"light": "#222222",
},
},
"flexDirection": "row", "flexDirection": "row",
"gap": 8, "gap": 8,
"marginLeft": 24, "marginLeft": 24,
...@@ -520,8 +480,6 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -520,8 +480,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
...@@ -643,8 +601,6 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -643,8 +601,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
<Text <Text
allowFontScaling={true} allowFontScaling={true}
maxFontSizeMultiplier={1.2} maxFontSizeMultiplier={1.2}
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"color": { "color": {
......
...@@ -7,7 +7,6 @@ import { ...@@ -7,7 +7,6 @@ import {
import { NativeStackNavigationProp, NativeStackScreenProps } from '@react-navigation/native-stack' import { NativeStackNavigationProp, NativeStackScreenProps } from '@react-navigation/native-stack'
import { TokenWarningModalState } from 'src/app/modals/TokenWarningModalState' import { TokenWarningModalState } from 'src/app/modals/TokenWarningModalState'
import { RemoveWalletModalState } from 'src/components/RemoveWallet/RemoveWalletModalState' import { RemoveWalletModalState } from 'src/components/RemoveWallet/RemoveWalletModalState'
import { RestoreWalletModalState } from 'src/components/RestoreWalletModal/RestoreWalletModalState'
import { ConnectionsDappsListModalState } from 'src/components/Settings/ConnectionsDappModal/ConnectionsDappsListModalState' import { ConnectionsDappsListModalState } from 'src/components/Settings/ConnectionsDappModal/ConnectionsDappsListModalState'
import { EditWalletSettingsModalState } from 'src/components/Settings/EditWalletModal/EditWalletSettingsModalState' import { EditWalletSettingsModalState } from 'src/components/Settings/EditWalletModal/EditWalletSettingsModalState'
import { ManageWalletsModalState } from 'src/components/Settings/ManageWalletsModalState' import { ManageWalletsModalState } from 'src/components/Settings/ManageWalletsModalState'
...@@ -17,9 +16,9 @@ import { ScantasticModalState } from 'src/features/scantastic/ScantasticModalSta ...@@ -17,9 +16,9 @@ import { ScantasticModalState } from 'src/features/scantastic/ScantasticModalSta
import { TestnetSwitchModalState } from 'src/features/testnetMode/TestnetSwitchModalState' import { TestnetSwitchModalState } from 'src/features/testnetMode/TestnetSwitchModalState'
import { HomeScreenTabIndex } from 'src/screens/HomeScreen/HomeScreenTabIndex' import { HomeScreenTabIndex } from 'src/screens/HomeScreen/HomeScreenTabIndex'
import { ReceiveCryptoModalState } from 'src/screens/ReceiveCryptoModalState' import { ReceiveCryptoModalState } from 'src/screens/ReceiveCryptoModalState'
import { ViewPrivateKeysScreenState } from 'src/screens/ViewPrivateKeys/ViewPrivateKeysScreenState'
import { FORServiceProvider } from 'uniswap/src/features/fiatOnRamp/types' import { FORServiceProvider } from 'uniswap/src/features/fiatOnRamp/types'
import { PasskeyManagementModalState } from 'uniswap/src/features/passkey/PasskeyManagementModal' import { PasskeyManagementModalState } from 'uniswap/src/features/passkey/PasskeyManagementModal'
import { SmartWalletAdvancedSettingsModalState } from 'uniswap/src/features/smartWallet/modals/SmartWalletAdvancedSettingsModal'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TestnetModeModalState } from 'uniswap/src/features/testnets/TestnetModeModal' import { TestnetModeModalState } from 'uniswap/src/features/testnets/TestnetModeModal'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
...@@ -30,12 +29,7 @@ import { ...@@ -30,12 +29,7 @@ import {
SharedUnitagScreenParams, SharedUnitagScreenParams,
UnitagStackParamList, UnitagStackParamList,
} from 'uniswap/src/types/screens/mobile' } from 'uniswap/src/types/screens/mobile'
import { PostSwapSmartWalletNudgeState } from 'wallet/src/components/smartWallet/modals/PostSwapSmartWalletNudge'
import { SmartWalletEnabledModalState } from 'wallet/src/components/smartWallet/modals/SmartWalletEnabledModal'
import { NFTItem } from 'wallet/src/features/nfts/types' import { NFTItem } from 'wallet/src/features/nfts/types'
import { SmartWalletAdvancedSettingsModalState } from 'wallet/src/features/smartWallet/modals/SmartWalletAdvancedSettingsModal'
import { SmartWalletConfirmModalState } from 'wallet/src/features/smartWallet/modals/SmartWalletConfirmModal'
import { SmartWalletInsufficientFundsOnNetworkModalState } from 'wallet/src/features/smartWallet/modals/SmartWalletInsufficientFundsOnNetworkModal'
type NFTItemScreenParams = { type NFTItemScreenParams = {
owner?: Address owner?: Address
...@@ -96,17 +90,15 @@ export type SettingsStackParamList = { ...@@ -96,17 +90,15 @@ export type SettingsStackParamList = {
[MobileScreens.SettingsLanguage]: undefined [MobileScreens.SettingsLanguage]: undefined
[MobileScreens.SettingsNotifications]: undefined [MobileScreens.SettingsNotifications]: undefined
[MobileScreens.SettingsPrivacy]: undefined [MobileScreens.SettingsPrivacy]: undefined
[MobileScreens.SettingsSmartWallet]: undefined
[MobileScreens.SettingsViewSeedPhrase]: { address: Address; walletNeedsRestore?: boolean } [MobileScreens.SettingsViewSeedPhrase]: { address: Address; walletNeedsRestore?: boolean }
[MobileScreens.SettingsWallet]: { address: Address } [MobileScreens.SettingsWallet]: { address: Address }
[MobileScreens.SettingsWalletEdit]: { address: Address } [MobileScreens.SettingsWalletEdit]: { address: Address }
[MobileScreens.SettingsWalletManageConnection]: { address: Address } [MobileScreens.SettingsWalletManageConnection]: { address: Address }
[MobileScreens.ViewPrivateKeys]?: ViewPrivateKeysScreenState
[MobileScreens.WebView]: { headerTitle: string; uriLink: string } [MobileScreens.WebView]: { headerTitle: string; uriLink: string }
[ModalName.Experiments]: undefined
[ModalName.NotificationsOSSettings]: undefined [ModalName.NotificationsOSSettings]: undefined
[ModalName.SettingsAppearance]: undefined
[ModalName.UnitagsIntro]: UnitagsIntroModalState [ModalName.UnitagsIntro]: UnitagsIntroModalState
[ModalName.RestoreWallet]: RestoreWalletModalState [ModalName.RestoreWallet]: undefined
} }
export type OnboardingStackBaseParams = { export type OnboardingStackBaseParams = {
...@@ -126,11 +118,9 @@ export type OnboardingStackParamList = { ...@@ -126,11 +118,9 @@ export type OnboardingStackParamList = {
[OnboardingScreens.WelcomeWallet]: OnboardingStackBaseParams [OnboardingScreens.WelcomeWallet]: OnboardingStackBaseParams
[OnboardingScreens.PasskeyImport]: PasskeyImportParams & OnboardingStackBaseParams [OnboardingScreens.PasskeyImport]: PasskeyImportParams & OnboardingStackBaseParams
[OnboardingScreens.Security]: OnboardingStackBaseParams [OnboardingScreens.Security]: OnboardingStackBaseParams
[MobileScreens.ViewPrivateKeys]?: ViewPrivateKeysScreenState
// import // import
[OnboardingScreens.ImportMethod]: OnboardingStackBaseParams [OnboardingScreens.ImportMethod]: OnboardingStackBaseParams
[OnboardingScreens.RestoreMethod]: OnboardingStackBaseParams
[OnboardingScreens.OnDeviceRecovery]: OnboardingStackBaseParams & { mnemonicIds: Address[] } [OnboardingScreens.OnDeviceRecovery]: OnboardingStackBaseParams & { mnemonicIds: Address[] }
[OnboardingScreens.OnDeviceRecoveryViewSeedPhrase]: { [OnboardingScreens.OnDeviceRecoveryViewSeedPhrase]: {
mnemonicId: string mnemonicId: string
...@@ -140,12 +130,9 @@ export type OnboardingStackParamList = { ...@@ -140,12 +130,9 @@ export type OnboardingStackParamList = {
[OnboardingScreens.RestoreCloudBackupPassword]: { [OnboardingScreens.RestoreCloudBackupPassword]: {
mnemonicId: string mnemonicId: string
} & OnboardingStackBaseParams } & OnboardingStackBaseParams
[OnboardingScreens.SeedPhraseInput]: OnboardingStackBaseParams & { [OnboardingScreens.SeedPhraseInput]: OnboardingStackBaseParams
showAsCloudBackupFallback?: boolean
}
[OnboardingScreens.SelectWallet]: OnboardingStackBaseParams [OnboardingScreens.SelectWallet]: OnboardingStackBaseParams
[OnboardingScreens.WatchWallet]: OnboardingStackBaseParams [OnboardingScreens.WatchWallet]: OnboardingStackBaseParams
[ModalName.PrivateKeySpeedBumpModal]: undefined
} & SharedUnitagScreenParams } & SharedUnitagScreenParams
export type AppStackParamList = { export type AppStackParamList = {
...@@ -164,7 +151,6 @@ export type AppStackParamList = { ...@@ -164,7 +151,6 @@ export type AppStackParamList = {
[MobileScreens.ExternalProfile]: { [MobileScreens.ExternalProfile]: {
address: string address: string
} }
[MobileScreens.ViewPrivateKeys]?: ViewPrivateKeysScreenState
[MobileScreens.WebView]: { headerTitle: string; uriLink: string } [MobileScreens.WebView]: { headerTitle: string; uriLink: string }
[MobileScreens.Storybook]: undefined [MobileScreens.Storybook]: undefined
[ModalName.Explore]: ExploreModalState | undefined [ModalName.Explore]: ExploreModalState | undefined
...@@ -177,7 +163,7 @@ export type AppStackParamList = { ...@@ -177,7 +163,7 @@ export type AppStackParamList = {
[ModalName.TokenWarning]: { initialState?: TokenWarningModalState } [ModalName.TokenWarning]: { initialState?: TokenWarningModalState }
[ModalName.ViewOnlyExplainer]: undefined [ModalName.ViewOnlyExplainer]: undefined
[ModalName.UnitagsIntro]: UnitagsIntroModalState [ModalName.UnitagsIntro]: UnitagsIntroModalState
[ModalName.RestoreWallet]: RestoreWalletModalState [ModalName.RestoreWallet]: undefined
[ModalName.AccountSwitcher]: undefined [ModalName.AccountSwitcher]: undefined
[ModalName.Scantastic]: ScantasticModalState [ModalName.Scantastic]: ScantasticModalState
[ModalName.BackupReminder]: undefined [ModalName.BackupReminder]: undefined
...@@ -196,16 +182,7 @@ export type AppStackParamList = { ...@@ -196,16 +182,7 @@ export type AppStackParamList = {
[ModalName.EditLabelSettingsModal]: EditWalletSettingsModalState [ModalName.EditLabelSettingsModal]: EditWalletSettingsModalState
[ModalName.EditProfileSettingsModal]: EditWalletSettingsModalState [ModalName.EditProfileSettingsModal]: EditWalletSettingsModalState
[ModalName.ConnectionsDappListModal]: ConnectionsDappsListModalState [ModalName.ConnectionsDappListModal]: ConnectionsDappsListModalState
[ModalName.SmartWalletEnabledModal]: SmartWalletEnabledModalState
[ModalName.SmartWalletAdvancedSettingsModal]: SmartWalletAdvancedSettingsModalState [ModalName.SmartWalletAdvancedSettingsModal]: SmartWalletAdvancedSettingsModalState
[ModalName.PrivateKeySpeedBumpModal]: undefined
[ModalName.SmartWalletConfirmModal]: SmartWalletConfirmModalState
[ModalName.SmartWalletInsufficientFundsOnNetworkModal]: SmartWalletInsufficientFundsOnNetworkModalState
[ModalName.PostSwapSmartWalletNudge]: PostSwapSmartWalletNudgeState
[ModalName.SettingsAppearance]: undefined
[ModalName.PermissionsModal]: undefined
[ModalName.PortfolioBalanceModal]: undefined
[ModalName.LanguageSelector]: undefined
} }
export type AppStackNavigationProp = NativeStackNavigationProp<AppStackParamList> export type AppStackNavigationProp = NativeStackNavigationProp<AppStackParamList>
......
...@@ -16,9 +16,9 @@ import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga' ...@@ -16,9 +16,9 @@ import { restoreMnemonicCompleteWatcher } from 'src/features/wallet/saga'
import { walletConnectSaga } from 'src/features/walletConnect/saga' import { walletConnectSaga } from 'src/features/walletConnect/saga'
import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga' import { signWcRequestSaga } from 'src/features/walletConnect/signWcRequestSaga'
import { call, fork, join, select, spawn, take } from 'typed-redux-saga' import { call, fork, join, select, spawn, take } from 'typed-redux-saga'
import { appLanguageWatcherSaga } from 'uniswap/src/features/language/saga'
import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient' import { apolloClientRef } from 'wallet/src/data/apollo/usePersistedApolloClient'
import { deviceLocaleWatcher } from 'wallet/src/features/i18n/deviceLocaleWatcherSaga' import { transactionWatcher } from 'wallet/src/features/transactions/transactionWatcherSaga'
import { transactionWatcher } from 'wallet/src/features/transactions/watcher/transactionWatcherSaga'
// These sagas are not persisted, so we can run them before rehydration // These sagas are not persisted, so we can run them before rehydration
const nonPersistedSagas = [appStateSaga, splashScreenSaga, biometricsSaga] const nonPersistedSagas = [appStateSaga, splashScreenSaga, biometricsSaga]
...@@ -26,6 +26,7 @@ const nonPersistedSagas = [appStateSaga, splashScreenSaga, biometricsSaga] ...@@ -26,6 +26,7 @@ const nonPersistedSagas = [appStateSaga, splashScreenSaga, biometricsSaga]
// All regular sagas must be included here // All regular sagas must be included here
const sagas = [ const sagas = [
lockScreenSaga, lockScreenSaga,
appLanguageWatcherSaga,
appRatingWatcherSaga, appRatingWatcherSaga,
cloudBackupsManagerSaga, cloudBackupsManagerSaga,
deepLinkWatcher, deepLinkWatcher,
...@@ -36,10 +37,9 @@ const sagas = [ ...@@ -36,10 +37,9 @@ const sagas = [
signWcRequestSaga, signWcRequestSaga,
telemetrySaga, telemetrySaga,
walletConnectSaga, walletConnectSaga,
deviceLocaleWatcher,
] ]
export function* rootMobileSaga(): SagaIterator { export function* rootMobileSaga() {
// Start non-persisted sagas // Start non-persisted sagas
for (const s of nonPersistedSagas) { for (const s of nonPersistedSagas) {
yield* spawn(s) yield* spawn(s)
......
...@@ -10,7 +10,6 @@ import { isNonJestDev } from 'utilities/src/environment/constants' ...@@ -10,7 +10,6 @@ import { isNonJestDev } from 'utilities/src/environment/constants'
import { createDatadogReduxEnhancer } from 'utilities/src/logger/datadog/Datadog' import { createDatadogReduxEnhancer } from 'utilities/src/logger/datadog/Datadog'
import { createStore } from 'wallet/src/state' import { createStore } from 'wallet/src/state'
import { createMigrate } from 'wallet/src/state/createMigrate' import { createMigrate } from 'wallet/src/state/createMigrate'
import { setReduxPersistor } from 'wallet/src/state/persistor'
const storage = new MMKV() const storage = new MMKV()
...@@ -67,6 +66,6 @@ const setupStore = ( ...@@ -67,6 +66,6 @@ const setupStore = (
enhancers, enhancers,
}) })
} }
export const store = setupStore() export const store = setupStore()
setReduxPersistor(persistStore(store))
export const persistor = persistStore(store)
...@@ -69,9 +69,9 @@ export function ConnectedDappsList({ backButton, sessions, selectedAddress }: Co ...@@ -69,9 +69,9 @@ export function ConnectedDappsList({ backButton, sessions, selectedAddress }: Co
pushNotification({ pushNotification({
type: AppNotificationType.WalletConnect, type: AppNotificationType.WalletConnect,
address, address,
dappName: session.dappRequestInfo.name, dappName: session.dapp.name,
event: WalletConnectEvent.Disconnected, event: WalletConnectEvent.Disconnected,
imageUrl: session.dappRequestInfo.icon, imageUrl: session.dapp.icon,
hideDelay: 3 * ONE_SECOND_MS, hideDelay: 3 * ONE_SECOND_MS,
}), }),
) )
......
...@@ -20,7 +20,7 @@ export function DappConnectionItem({ ...@@ -20,7 +20,7 @@ export function DappConnectionItem({
handleDisconnect: (session: WalletConnectSession) => Promise<void> handleDisconnect: (session: WalletConnectSession) => Promise<void>
}): JSX.Element { }): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const { dappRequestInfo } = session const { dapp } = session
const menuActions = [{ title: t('common.button.disconnect'), systemIcon: 'trash', destructive: true }] const menuActions = [{ title: t('common.button.disconnect'), systemIcon: 'trash', destructive: true }]
...@@ -74,12 +74,12 @@ export function DappConnectionItem({ ...@@ -74,12 +74,12 @@ export function DappConnectionItem({
)} )}
</Flex> </Flex>
<Flex grow centered gap="$gap8"> <Flex grow centered gap="$gap8">
<DappHeaderIcon size={iconSizes.icon36} dappRequestInfo={dappRequestInfo} /> <DappHeaderIcon size={iconSizes.icon36} dapp={dapp} />
<Text numberOfLines={2} textAlign="center" variant="body3" mt="$spacing4"> <Text numberOfLines={2} textAlign="center" variant="body3" mt="$spacing4">
{dappRequestInfo.name || dappRequestInfo.url} {dapp.name || dapp.url}
</Text> </Text>
<Text color="$neutral2" numberOfLines={1} textAlign="center" variant="body4"> <Text color="$neutral2" numberOfLines={1} textAlign="center" variant="body4">
{dappRequestInfo.url} {dapp.url}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -3,15 +3,15 @@ import { Flex, UniversalImage } from 'ui/src' ...@@ -3,15 +3,15 @@ import { Flex, UniversalImage } from 'ui/src'
import { borderRadii, iconSizes } from 'ui/src/theme' import { borderRadii, iconSizes } from 'ui/src/theme'
import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { DappRequestInfo } from 'uniswap/src/types/walletConnect' import { DappInfo } from 'uniswap/src/types/walletConnect'
import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder' import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder'
export function DappHeaderIcon({ export function DappHeaderIcon({
dappRequestInfo, dapp,
permitCurrencyInfo, permitCurrencyInfo,
size = iconSizes.icon40, size = iconSizes.icon40,
}: { }: {
dappRequestInfo: DappRequestInfo dapp: DappInfo
permitCurrencyInfo?: CurrencyInfo | null permitCurrencyInfo?: CurrencyInfo | null
size?: number size?: number
}): JSX.Element { }): JSX.Element {
...@@ -19,11 +19,11 @@ export function DappHeaderIcon({ ...@@ -19,11 +19,11 @@ export function DappHeaderIcon({
return <CurrencyLogo currencyInfo={permitCurrencyInfo} /> return <CurrencyLogo currencyInfo={permitCurrencyInfo} />
} }
const fallback = <DappIconPlaceholder iconSize={size} name={dappRequestInfo.name} /> const fallback = <DappIconPlaceholder iconSize={size} name={dapp.name} />
return ( return (
<Flex height={size} width={size}> <Flex height={size} width={size}>
{dappRequestInfo.icon ? ( {dapp.icon ? (
<UniversalImage <UniversalImage
fallback={fallback} fallback={fallback}
size={{ height: size, width: size }} size={{ height: size, width: size }}
...@@ -34,7 +34,7 @@ export function DappHeaderIcon({ ...@@ -34,7 +34,7 @@ export function DappHeaderIcon({
overflow: 'hidden', overflow: 'hidden',
}, },
}} }}
uri={dappRequestInfo.icon} uri={dapp.icon}
/> />
) : ( ) : (
fallback fallback
......
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
} from 'react-native' } from 'react-native'
import { AnimatedStyle, useDerivedValue } from 'react-native-reanimated' import { AnimatedStyle, useDerivedValue } from 'react-native-reanimated'
import { ScrollDownOverlay } from 'src/components/Requests/ModalWithOverlay/ScrollDownOverlay' import { ScrollDownOverlay } from 'src/components/Requests/ModalWithOverlay/ScrollDownOverlay'
import { Button, ButtonProps, Flex } from 'ui/src' import { Button, Flex } from 'ui/src'
import { spacing } from 'ui/src/theme' import { spacing } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalProps } from 'uniswap/src/components/modals/ModalProps' import { ModalProps } from 'uniswap/src/components/modals/ModalProps'
...@@ -24,17 +24,14 @@ import { TestID } from 'uniswap/src/test/fixtures/testIDs' ...@@ -24,17 +24,14 @@ import { TestID } from 'uniswap/src/test/fixtures/testIDs'
const MEASURE_LAYOUT_TIMEOUT = 100 const MEASURE_LAYOUT_TIMEOUT = 100
export type ModalWithOverlayProps = PropsWithChildren< type ModalWithOverlayProps = PropsWithChildren<
ModalProps & { ModalProps & {
confirmationButtonText?: string confirmationButtonText?: string
cancelButtonText?: string
scrollDownButtonText?: string scrollDownButtonText?: string
onReject: () => void onReject: () => void
onConfirm?: () => void onConfirm?: () => void
disableConfirm?: boolean disableConfirm?: boolean
contentContainerStyle?: StyleProp<AnimatedStyle<StyleProp<ViewStyle>>> contentContainerStyle?: StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>
cancelButtonProps?: ButtonProps
confirmationButtonProps?: ButtonProps
} }
> >
...@@ -45,14 +42,11 @@ const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }: Nati ...@@ -45,14 +42,11 @@ const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }: Nati
export function ModalWithOverlay({ export function ModalWithOverlay({
children, children,
confirmationButtonText, confirmationButtonText,
cancelButtonText,
scrollDownButtonText, scrollDownButtonText,
onReject, onReject,
onConfirm, onConfirm,
disableConfirm, disableConfirm,
contentContainerStyle, contentContainerStyle,
cancelButtonProps,
confirmationButtonProps,
...bottomSheetModalProps ...bottomSheetModalProps
}: ModalWithOverlayProps): JSX.Element { }: ModalWithOverlayProps): JSX.Element {
const scrollViewRef = useRef<ScrollView>(null) const scrollViewRef = useRef<ScrollView>(null)
...@@ -135,13 +129,10 @@ export function ModalWithOverlay({ ...@@ -135,13 +129,10 @@ export function ModalWithOverlay({
</BottomSheetScrollView> </BottomSheetScrollView>
<ModalFooter <ModalFooter
cancelButtonText={cancelButtonText}
confirmationButtonText={confirmationButtonText} confirmationButtonText={confirmationButtonText}
confirmationEnabled={!disableConfirm && confirmationEnabled} confirmationEnabled={!disableConfirm && confirmationEnabled}
scrollDownButtonText={scrollDownButtonText} scrollDownButtonText={scrollDownButtonText}
showScrollDownOverlay={showOverlay && !eip5792MethodsEnabled} showScrollDownOverlay={showOverlay && !eip5792MethodsEnabled}
cancelButtonProps={cancelButtonProps}
confirmationButtonProps={confirmationButtonProps}
onConfirm={onConfirm} onConfirm={onConfirm}
onReject={onReject} onReject={onReject}
onScrollDownPress={handleScrollDown} onScrollDownPress={handleScrollDown}
...@@ -153,11 +144,8 @@ export function ModalWithOverlay({ ...@@ -153,11 +144,8 @@ export function ModalWithOverlay({
type ModalFooterProps = { type ModalFooterProps = {
confirmationEnabled: boolean confirmationEnabled: boolean
showScrollDownOverlay: boolean showScrollDownOverlay: boolean
cancelButtonText?: string
confirmationButtonText?: string confirmationButtonText?: string
scrollDownButtonText?: string scrollDownButtonText?: string
cancelButtonProps?: ButtonProps
confirmationButtonProps?: ButtonProps
onScrollDownPress: () => void onScrollDownPress: () => void
onReject: () => void onReject: () => void
onConfirm?: () => void onConfirm?: () => void
...@@ -167,10 +155,7 @@ function ModalFooter({ ...@@ -167,10 +155,7 @@ function ModalFooter({
confirmationEnabled, confirmationEnabled,
showScrollDownOverlay, showScrollDownOverlay,
scrollDownButtonText, scrollDownButtonText,
cancelButtonText,
confirmationButtonText, confirmationButtonText,
cancelButtonProps,
confirmationButtonProps,
onScrollDownPress, onScrollDownPress,
onReject, onReject,
onConfirm, onConfirm,
...@@ -203,8 +188,8 @@ function ModalFooter({ ...@@ -203,8 +188,8 @@ function ModalFooter({
pt="$spacing12" pt="$spacing12"
px="$spacing24" px="$spacing24"
> >
<Button size="large" testID={TestID.Cancel} emphasis="tertiary" onPress={onReject} {...cancelButtonProps}> <Button size="large" testID={TestID.Cancel} emphasis="tertiary" onPress={onReject}>
{cancelButtonText ?? t('common.button.cancel')} {t('common.button.cancel')}
</Button> </Button>
{confirmationButtonText && ( {confirmationButtonText && (
...@@ -214,7 +199,6 @@ function ModalFooter({ ...@@ -214,7 +199,6 @@ function ModalFooter({
size="large" size="large"
testID={TestID.Confirm} testID={TestID.Confirm}
onPress={onConfirm} onPress={onConfirm}
{...confirmationButtonProps}
> >
{confirmationButtonText} {confirmationButtonText}
</Button> </Button>
......
...@@ -20,26 +20,26 @@ export function ClientDetails({ ...@@ -20,26 +20,26 @@ export function ClientDetails({
request: WalletConnectSigningRequest request: WalletConnectSigningRequest
permitInfo?: PermitInfo permitInfo?: PermitInfo
}): JSX.Element { }): JSX.Element {
const { dappRequestInfo } = request const { dapp } = request
const colors = useSporeColors() const colors = useSporeColors()
const permitCurrencyInfo = useCurrencyInfo(permitInfo?.currencyId) const permitCurrencyInfo = useCurrencyInfo(permitInfo?.currencyId)
return ( return (
<Flex centered gap="$spacing12"> <Flex centered gap="$spacing12">
<DappHeaderIcon dappRequestInfo={dappRequestInfo} permitCurrencyInfo={permitCurrencyInfo} /> <DappHeaderIcon dapp={dapp} permitCurrencyInfo={permitCurrencyInfo} />
<HeaderText permitAmount={permitInfo?.amount} permitCurrency={permitCurrencyInfo?.currency} request={request} /> <HeaderText permitAmount={permitInfo?.amount} permitCurrency={permitCurrencyInfo?.currency} request={request} />
<LinkButton <LinkButton
color={colors.accent1.val} color={colors.accent1.val}
iconColor="$accent1" iconColor="$accent1"
label={formatDappURL(dappRequestInfo.url)} label={formatDappURL(dapp.url)}
mb="$spacing12" mb="$spacing12"
px="$spacing8" px="$spacing8"
py="$spacing4" py="$spacing4"
showIcon={false} showIcon={false}
size={iconSizes.icon12} size={iconSizes.icon12}
textVariant="buttonLabel2" textVariant="buttonLabel2"
url={dappRequestInfo.url} url={dapp.url}
/> />
</Flex> </Flex>
) )
......
...@@ -16,7 +16,7 @@ export function HeaderText({ ...@@ -16,7 +16,7 @@ export function HeaderText({
permitAmount?: number permitAmount?: number
permitCurrency?: Currency | null permitCurrency?: Currency | null
}): JSX.Element { }): JSX.Element {
const { dappRequestInfo, type: method } = request const { dapp, type: method } = request
if (permitCurrency) { if (permitCurrency) {
const readablePermitAmount = getCurrencyAmount({ const readablePermitAmount = getCurrencyAmount({
...@@ -32,7 +32,7 @@ export function HeaderText({ ...@@ -32,7 +32,7 @@ export function HeaderText({
components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }} components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }}
i18nKey="qrScanner.request.withAmount" i18nKey="qrScanner.request.withAmount"
values={{ values={{
dappName: dappRequestInfo.name, dappName: dapp.name,
currencySymbol: permitCurrency?.symbol, currencySymbol: permitCurrency?.symbol,
amount: readablePermitAmount, amount: readablePermitAmount,
}} }}
...@@ -45,7 +45,7 @@ export function HeaderText({ ...@@ -45,7 +45,7 @@ export function HeaderText({
components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }} components={{ highlight: <Text variant="heading3" fontWeight="bold" /> }}
i18nKey="qrScanner.request.withoutAmount" i18nKey="qrScanner.request.withoutAmount"
values={{ values={{
dappName: dappRequestInfo.name, dappName: dapp.name,
currencySymbol: permitCurrency?.symbol, currencySymbol: permitCurrency?.symbol,
}} }}
/> />
...@@ -72,7 +72,7 @@ export function HeaderText({ ...@@ -72,7 +72,7 @@ export function HeaderText({
return ( return (
<Text textAlign="center" variant="subheading1"> <Text textAlign="center" variant="subheading1">
{getReadableMethodName(method, dappRequestInfo.name || dappRequestInfo.url)} {getReadableMethodName(method, dapp.name || dapp.url)}
</Text> </Text>
) )
} }
...@@ -29,11 +29,13 @@ import { spacing } from 'ui/src/theme' ...@@ -29,11 +29,13 @@ import { spacing } from 'ui/src/theme'
import { EthMethod } from 'uniswap/src/features/dappRequests/types' import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { isSignTypedDataRequest } from 'uniswap/src/features/dappRequests/utils' import { isSignTypedDataRequest } from 'uniswap/src/features/dappRequests/utils'
import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks' import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { useHasAccountMismatchCallback } from 'uniswap/src/features/smartWallet/mismatch/hooks' import { useHasAccountMismatchCallback } from 'uniswap/src/features/smartWallet/mismatch/hooks'
import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { useIsBlocked } from 'uniswap/src/features/trm/hooks' import { useIsBlocked } from 'uniswap/src/features/trm/hooks'
import { DappRequestType, UwULinkMethod, WCEventType, WCRequestOutcome } from 'uniswap/src/types/walletConnect' import { UwULinkMethod, WCEventType, WCRequestOutcome } from 'uniswap/src/types/walletConnect'
import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { areAddressesEqual } from 'uniswap/src/utils/addresses'
import { formatExternalTxnWithGasEstimates } from 'wallet/src/features/gas/formatExternalTxnWithGasEstimates' import { formatExternalTxnWithGasEstimates } from 'wallet/src/features/gas/formatExternalTxnWithGasEstimates'
import { useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks' import { useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks'
...@@ -60,6 +62,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -60,6 +62,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
const didOpenFromDeepLink = useSelector(selectDidOpenFromDeepLink) const didOpenFromDeepLink = useSelector(selectDidOpenFromDeepLink)
const chainId = request.chainId const chainId = request.chainId
const enablePermitMismatchUx = useFeatureFlag(FeatureFlags.EnablePermitMismatchUX)
const tx: providers.TransactionRequest | undefined = useMemo(() => { const tx: providers.TransactionRequest | undefined = useMemo(() => {
if (isTransactionRequest(request)) { if (isTransactionRequest(request)) {
return { ...request.transaction, chainId } return { ...request.transaction, chainId }
...@@ -89,11 +93,9 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -89,11 +93,9 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
const getHasMismatch = useHasAccountMismatchCallback() const getHasMismatch = useHasAccountMismatchCallback()
const hasMismatch = chainId ? getHasMismatch(chainId) : false const hasMismatch = chainId ? getHasMismatch(chainId) : false
// When link mode is active we can sign messages through universal links on device
const suppressOfflineWarning = request.isLinkModeSupported
const checkConfirmEnabled = (): boolean => { const checkConfirmEnabled = (): boolean => {
if (!netInfo.isInternetReachable && !suppressOfflineWarning) { if (!netInfo.isInternetReachable) {
return false return false
} }
...@@ -125,7 +127,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -125,7 +127,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
const rejectOnCloseRef = useRef(true) const rejectOnCloseRef = useRef(true)
const onReject = async (): Promise<void> => { const onReject = async (): Promise<void> => {
if (request.dappRequestInfo.requestType === DappRequestType.WalletConnectSessionRequest) { if (request.dapp.source === 'walletconnect') {
await wcWeb3Wallet.respondSessionRequest({ await wcWeb3Wallet.respondSessionRequest({
topic: request.sessionId, topic: request.sessionId,
response: { response: {
...@@ -141,8 +143,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -141,8 +143,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
sendAnalyticsEvent(MobileEventName.WalletConnectSheetCompleted, { sendAnalyticsEvent(MobileEventName.WalletConnectSheetCompleted, {
request_type: isTransactionRequest(request) ? WCEventType.TransactionRequest : WCEventType.SignRequest, request_type: isTransactionRequest(request) ? WCEventType.TransactionRequest : WCEventType.SignRequest,
eth_method: request.type, eth_method: request.type,
dapp_url: request.dappRequestInfo.url, dapp_url: request.dapp.url,
dapp_name: request.dappRequestInfo.name, dapp_name: request.dapp.name,
wc_version: '2', wc_version: '2',
chain_id: chainId, chain_id: chainId,
outcome: WCRequestOutcome.Reject, outcome: WCRequestOutcome.Reject,
...@@ -180,7 +182,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -180,7 +182,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
method: request.type === EthMethod.WalletSendCalls ? EthMethod.WalletSendCalls : EthMethod.EthSendTransaction, method: request.type === EthMethod.WalletSendCalls ? EthMethod.WalletSendCalls : EthMethod.EthSendTransaction,
transaction: txnWithFormattedGasEstimates, transaction: txnWithFormattedGasEstimates,
account: signerAccount, account: signerAccount,
dappRequestInfo: request.dappRequestInfo, dapp: request.dapp,
chainId, chainId,
request, request,
}), }),
...@@ -194,7 +196,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -194,7 +196,7 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
method: request.type, method: request.type,
message: request.message || request.rawMessage, message: request.message || request.rawMessage,
account: signerAccount, account: signerAccount,
dappRequestInfo: request.dappRequestInfo, dapp: request.dapp,
chainId, chainId,
}), }),
) )
...@@ -205,8 +207,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -205,8 +207,8 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
sendAnalyticsEvent(MobileEventName.WalletConnectSheetCompleted, { sendAnalyticsEvent(MobileEventName.WalletConnectSheetCompleted, {
request_type: isTransactionRequest(request) ? WCEventType.TransactionRequest : WCEventType.SignRequest, request_type: isTransactionRequest(request) ? WCEventType.TransactionRequest : WCEventType.SignRequest,
eth_method: request.type, eth_method: request.type,
dapp_url: request.dappRequestInfo.url, dapp_url: request.dapp.url,
dapp_name: request.dappRequestInfo.name, dapp_name: request.dapp.name,
wc_version: '2', wc_version: '2',
chain_id: chainId, chain_id: chainId,
outcome: WCRequestOutcome.Confirm, outcome: WCRequestOutcome.Confirm,
...@@ -256,12 +258,12 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem ...@@ -256,12 +258,12 @@ export function WalletConnectRequestModal({ onClose, request }: Props): JSX.Elem
) )
} }
if (hasMismatch && isSignTypedDataRequest(request)) { if (enablePermitMismatchUx && hasMismatch && isSignTypedDataRequest(request)) {
return <ActionCannotBeCompletedContent request={request} onReject={onReject} /> return <ActionCannotBeCompletedContent request={request} onReject={onReject} />
} }
// KidSuper Uniswap Cafe check-in screen // KidSuper Uniswap Cafe check-in screen
if (request.type === EthMethod.PersonalSign && request.dappRequestInfo.name === 'Uniswap Cafe') { if (request.type === EthMethod.PersonalSign && request.dapp.name === 'Uniswap Cafe') {
return ( return (
<KidSuperCheckinModal request={request} onClose={handleClose} onConfirm={onConfirmPress} onReject={onReject} /> <KidSuperCheckinModal request={request} onClose={handleClose} onConfirm={onConfirmPress} onReject={onReject} />
) )
......
...@@ -82,9 +82,6 @@ export function WalletConnectRequestModalContent({ ...@@ -82,9 +82,6 @@ export function WalletConnectRequestModalContent({
const hasGasFee = getDoesMethodCostGas(request) const hasGasFee = getDoesMethodCostGas(request)
// If link mode is supported, we can sign messages through universal links on device
const suppressOfflineWarning = request.isLinkModeSupported
return ( return (
<> <>
<Flex px="$spacing24"> <Flex px="$spacing24">
...@@ -121,7 +118,7 @@ export function WalletConnectRequestModalContent({ ...@@ -121,7 +118,7 @@ export function WalletConnectRequestModalContent({
</Flex> </Flex>
)} )}
{!netInfo.isInternetReachable && !suppressOfflineWarning ? ( {!netInfo.isInternetReachable ? (
<BaseCard.InlineErrorState <BaseCard.InlineErrorState
backgroundColor="$statusWarning2" backgroundColor="$statusWarning2"
icon={<AlertTriangleFilled color="$statusWarning" size="$icon.16" />} icon={<AlertTriangleFilled color="$statusWarning" size="$icon.16" />}
......
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { WalletConnectVerifyStatus } from 'src/features/walletConnect/walletConnectSlice'
import { Flex, LabeledCheckbox, Text, TouchableArea } from 'ui/src'
import {
AlertCircleFilled,
AlertTriangleFilled,
CheckCircleFilled,
Clear,
GlobeFilled,
OctagonExclamation,
RotatableChevron,
} from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { useBooleanState } from 'utilities/src/react/useBooleanState'
const PERMISSIONS_MAX_LENGTH = 280
export const SitePermissions = ({
verifyStatus,
confirmedWarning,
onConfirmWarning,
}: {
verifyStatus: WalletConnectVerifyStatus
confirmedWarning?: boolean
onConfirmWarning?: (confirmed: boolean) => void
}): JSX.Element => {
const { t } = useTranslation()
// Always show expanded permissions for unverified apps
const isInitiallyExpanded = verifyStatus !== WalletConnectVerifyStatus.Verified
const { value: isExpanded, toggle: toggleExpanded, setValue: setIsExpanded } = useBooleanState(isInitiallyExpanded)
const infoTextSize = 'body3'
const handleConfirmWarning = useCallback(
(previousIsConfirmed: boolean) => {
onConfirmWarning?.(!previousIsConfirmed)
// Open options if previously confirmed, close if previously unconfirmed
setIsExpanded(previousIsConfirmed ? true : false)
},
[onConfirmWarning, setIsExpanded],
)
return (
<Flex gap="$spacing12">
<Flex
backgroundColor="$surface2"
borderColor={verifyStatus === WalletConnectVerifyStatus.Threat ? '$statusCritical' : '$surface3'}
borderRadius="$rounded16"
borderWidth="$spacing1"
minHeight={44}
>
<TouchableArea
pb="$spacing12"
p="$spacing16"
borderBottomWidth={isExpanded ? 1 : 0}
borderColor="$surface3"
onPress={toggleExpanded}
>
<Flex centered row justifyContent="space-between">
<Flex centered row gap="$spacing8">
<GlobeFilled color="$neutral2" size="$icon.16" />
<Text $short={{ variant: 'body3' }} allowFontScaling={false} color="$neutral2" variant="buttonLabel3">
{t('walletConnect.permissions.title')}
</Text>
</Flex>
<RotatableChevron
color="$neutral2"
direction={isExpanded ? 'up' : 'down'}
height={iconSizes.icon16}
width={iconSizes.icon16}
/>
</Flex>
</TouchableArea>
{isExpanded && (
<Flex gap="$spacing12" p="$spacing16">
<Flex centered row gap="$spacing8">
<CheckCircleFilled color="$statusSuccess" size="$icon.16" />
<Text
$short={{ variant: infoTextSize }}
allowFontScaling={false}
color="$neutral2"
flexGrow={1}
variant={infoTextSize}
>
{t('walletConnect.permissions.option.viewTokenBalances')}
</Text>
</Flex>
<Flex centered row gap="$spacing8">
<CheckCircleFilled color="$statusSuccess" size="$icon.16" />
<Text
$short={{ variant: infoTextSize }}
allowFontScaling={false}
color="$neutral2"
flexGrow={1}
variant={infoTextSize}
>
{t('walletConnect.permissions.option.requestApprovals')}
</Text>
</Flex>
<Flex centered row gap="$spacing8">
<Clear color="$statusCritical" size="$icon.16" />
<Text
$short={{ variant: infoTextSize }}
allowFontScaling={false}
color="$neutral2"
flexGrow={1}
variant={infoTextSize}
>
{t('walletConnect.permissions.option.transferAssets')}
</Text>
</Flex>
</Flex>
)}
</Flex>
{verifyStatus === WalletConnectVerifyStatus.Unverified && (
<Flex row backgroundColor="$surface2" borderRadius="$rounded12" p="$spacing12" justifyContent="space-between">
<Flex row gap="$spacing12">
<AlertTriangleFilled color="$statusWarning" size="$icon.20" />
<Flex gap="$spacing8" maxWidth={PERMISSIONS_MAX_LENGTH}>
<Text color="$statusWarning" variant="buttonLabel3">
{t('walletConnect.pending.unverified.title')}
</Text>
<Text color="$neutral2" variant="body3" textWrap="wrap">
{t('walletConnect.pending.unverified.description')}
</Text>
</Flex>
</Flex>
<AlertCircleFilled color="$neutral3" size="$icon.20" />
</Flex>
)}
{verifyStatus === WalletConnectVerifyStatus.Threat && (
<Flex row backgroundColor="$surface2" borderRadius="$rounded12" p="$spacing12" justifyContent="space-between">
<Flex row gap="$spacing12">
<OctagonExclamation color="$statusCritical" size="$icon.20" />
<Flex gap="$spacing8" maxWidth={PERMISSIONS_MAX_LENGTH}>
<Text color="$statusCritical" variant="buttonLabel3">
{t('walletConnect.pending.threat.title')}
</Text>
<Text color="$neutral2" variant="body3" textWrap="wrap">
{t('walletConnect.pending.threat.description')}
</Text>
<LabeledCheckbox
checked={Boolean(confirmedWarning)}
checkboxPosition="start"
gap="$spacing8"
size="$icon.16"
px="$none"
text={
<Text color="$neutral2" flexShrink={1} variant="body3">
{t('walletConnect.pending.threat.confirmationText')}
</Text>
}
onCheckPressed={handleConfirmWarning}
/>
</Flex>
</Flex>
<AlertCircleFilled color="$neutral3" size="$icon.20" />
</Flex>
)}
</Flex>
)
}
...@@ -10,14 +10,7 @@ import { ...@@ -10,14 +10,7 @@ import {
} from 'uniswap/src/features/gating/configs' } from 'uniswap/src/features/gating/configs'
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { isUwULinkAllowlistType } from 'uniswap/src/features/gating/typeGuards' import { isUwULinkAllowlistType } from 'uniswap/src/features/gating/typeGuards'
import { import { EthTransaction, UwULinkErc20SendRequest, UwULinkMethod, UwULinkRequest } from 'uniswap/src/types/walletConnect'
DappRequestType,
EthTransaction,
UwULinkErc20SendRequest,
UwULinkMethod,
UwULinkRequest,
UwULinkRequestInfo,
} from 'uniswap/src/types/walletConnect'
import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { areAddressesEqual } from 'uniswap/src/utils/addresses'
import { ContractManager } from 'wallet/src/features/contracts/ContractManager' import { ContractManager } from 'wallet/src/features/contracts/ContractManager'
import { ProviderManager } from 'wallet/src/features/providers/ProviderManager' import { ProviderManager } from 'wallet/src/features/providers/ProviderManager'
...@@ -122,21 +115,15 @@ export async function getFormattedUwuLinkTxnRequest({ ...@@ -122,21 +115,15 @@ export async function getFormattedUwuLinkTxnRequest({
providerManager, providerManager,
contractManager, contractManager,
}: HandleUwuLinkRequestParams): Promise<{ request: WalletConnectSigningRequest; account: string }> { }: HandleUwuLinkRequestParams): Promise<{ request: WalletConnectSigningRequest; account: string }> {
const newRequest: { const newRequest = {
sessionId: string
internalId: string
account: string
dappRequestInfo: UwULinkRequestInfo
chainId: number
} = {
sessionId: UWULINK_PREFIX, // session/internalId is WalletConnect specific, but not needed here sessionId: UWULINK_PREFIX, // session/internalId is WalletConnect specific, but not needed here
internalId: UWULINK_PREFIX, internalId: UWULINK_PREFIX,
account: activeAccount?.address, account: activeAccount?.address,
dappRequestInfo: { dapp: {
name: '', name: '',
url: '', url: '',
...request.dapp, ...request.dapp,
requestType: DappRequestType.UwULink, source: UWULINK_PREFIX,
chain_id: request.chainId, chain_id: request.chainId,
webhook: request.webhook, webhook: request.webhook,
}, },
......
...@@ -61,10 +61,10 @@ export function WalletConnectModals(): JSX.Element { ...@@ -61,10 +61,10 @@ export function WalletConnectModals(): JSX.Element {
// When WalletConnectModal is open and a WC QR code is scanned to add a pendingSession, // When WalletConnectModal is open and a WC QR code is scanned to add a pendingSession,
// dismiss the scan modal in favor of showing PendingConnectionModal // dismiss the scan modal in favor of showing PendingConnectionModal
useEffect(() => { useEffect(() => {
if (modalState.isOpen && (pendingSession || currRequest)) { if (modalState.isOpen && pendingSession) {
dispatch(closeModal({ name: ModalName.WalletConnectScan })) dispatch(closeModal({ name: ModalName.WalletConnectScan }))
} }
}, [modalState.isOpen, pendingSession, currRequest, dispatch]) }, [modalState.isOpen, pendingSession, dispatch])
return ( return (
<> <>
......
import React from 'react'
import { PrivateKeySpeedBumpModal } from 'src/components/RestoreWalletModal/PrivateKeySpeedBumpModal'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { fireEvent, render } from 'src/test/test-utils'
import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
jest.mock('src/components/modals/useReactNavigationModal', () => ({
useReactNavigationModal: jest.fn(),
}))
jest.mock('@gorhom/bottom-sheet', () => {
const reactNative = jest.requireActual('react-native')
const { View } = reactNative
return {
__esModule: true,
default: View,
BottomSheetModal: View,
BottomSheetModalProvider: View,
BottomSheetView: View,
}
})
describe('PrivateKeySpeedBumpModal', () => {
const mockPreventCloseRef = { current: false }
const mockNavigation = { navigate: jest.fn() }
const mockOnClose = jest.fn()
beforeEach(() => {
jest.clearAllMocks()
;(useReactNavigationModal as jest.Mock).mockReturnValue({
onClose: mockOnClose,
preventCloseRef: mockPreventCloseRef,
})
})
it('renders correctly', () => {
// @ts-expect-error Mocking navigation object since it's not critical to this test
const { toJSON } = render(<PrivateKeySpeedBumpModal navigation={mockNavigation} />)
expect(toJSON()).toMatchSnapshot()
})
it('navigates to ViewPrivateKeys screen when Continue button is pressed', () => {
// @ts-expect-error Mocking navigation object since it's not critical to this test
const screen = render(<PrivateKeySpeedBumpModal navigation={mockNavigation} />)
const continueButton = screen.getByTestId(TestID.Continue)
fireEvent.press(continueButton)
expect(mockPreventCloseRef.current).toBe(true)
expect(mockNavigation.navigate).toHaveBeenCalledWith(MobileScreens.ViewPrivateKeys)
})
})
import React from 'react'
import { useTranslation } from 'react-i18next'
import { AppStackScreenProp } from 'src/app/navigation/types'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { Button, Flex, IconButton, InlineCard, Text, useSporeColors } from 'ui/src'
import { AlertTriangleFilled, Key } from 'ui/src/components/icons'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { SPACE_STRING } from 'utilities/src/primitives/string'
/**
* This modal is used as an informational speedbump before the user
* is sent to the screen to view their private keys.
*/
export function PrivateKeySpeedBumpModal({
navigation,
}: AppStackScreenProp<typeof ModalName.PrivateKeySpeedBumpModal>): JSX.Element | null {
const colors = useSporeColors()
const { onClose, preventCloseRef } = useReactNavigationModal()
const onContinue = (): void => {
preventCloseRef.current = true
navigation.navigate(MobileScreens.ViewPrivateKeys)
}
return (
<Modal backgroundColor={colors.surface1.val} name={ModalName.PrivateKeySpeedBumpModal} onClose={onClose}>
<PrivateKeySpeedBumpModalContent onClose={onClose} onContinue={onContinue} />
</Modal>
)
}
const PrivateKeySpeedBumpModalContent = ({
onClose,
onContinue,
}: {
onClose: () => void
onContinue: () => void
}): JSX.Element => {
const { t } = useTranslation()
return (
<Flex px="$spacing24" pt="$spacing8">
<Flex row justifyContent="center">
<IconButton size="medium" emphasis="secondary" icon={<Key />} onPress={onClose} />
</Flex>
<Text textAlign="center" variant="subheading1" pt="$spacing24">
{t('privateKeys.export.modal.title')}
</Text>
<Text textAlign="center" variant="body2" color="$neutral2" pt="$spacing8">
{t('privateKeys.export.modal.subtitle')}
<Text variant="body2" color="$neutral1" ml="$spacing4" onPress={() => {}}>
{/* TODO(ALL-6735): Add link to learn more about private keys */}
{SPACE_STRING + t('common.button.learn')}
</Text>
</Text>
<Flex pt="$spacing16">
<InlineCard
Icon={AlertTriangleFilled}
color="$neutral2"
description={
<Text variant="body3" color="$neutral2">
{t('privateKeys.export.modal.warning')}
</Text>
}
iconColor="$neutral2"
/>
</Flex>
<Flex row py="$spacing24">
<Button testID={TestID.Continue} variant="branded" emphasis="primary" size="medium" onPress={onContinue}>
{t('common.button.continue')}
</Button>
</Flex>
</Flex>
)
}
import React, { useMemo } from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { AppStackScreenProp } from 'src/app/navigation/types'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal' import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { WalletRestoreType } from 'src/components/RestoreWalletModal/RestoreWalletModalState'
import { closeAllModals } from 'src/features/modals/modalSlice' import { closeAllModals } from 'src/features/modals/modalSlice'
import { Button, Flex, useSporeColors } from 'ui/src' import { Button, Flex, useSporeColors } from 'ui/src'
import { ArrowDownCircleFilledWithBorder, WalletFilled } from 'ui/src/components/icons' import { ArrowDownCircle, WalletFilled } from 'ui/src/components/icons'
import { spacing } from 'ui/src/theme/spacing' import { spacing } from 'ui/src/theme/spacing'
import { GenericHeader } from 'uniswap/src/components/misc/GenericHeader' import { GenericHeader } from 'uniswap/src/components/misc/GenericHeader'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile'
...@@ -24,72 +21,43 @@ const SHADOW_OPACITY = 0.3 ...@@ -24,72 +21,43 @@ const SHADOW_OPACITY = 0.3
const SHADOW_OFFSET = { width: 0, height: 0 } as const const SHADOW_OFFSET = { width: 0, height: 0 } as const
const ICON_OFFSET = -spacing.spacing8 const ICON_OFFSET = -spacing.spacing8
/** function BackgroundRing({ size }: { size: number }): JSX.Element {
* This modal is used to prompt the user to restore their wallet depending on the type of return (
* restoration needed. <Flex
*/ position="absolute"
export function RestoreWalletModal({ route }: AppStackScreenProp<typeof ModalName.RestoreWallet>): JSX.Element | null { borderRadius="$roundedFull"
borderColor="$surface3"
borderWidth="$spacing1"
height={size}
width={size}
top="50%"
left="50%"
transform={[{ translateX: -size / 2 }, { translateY: -size / 2 }]}
/>
)
}
export function RestoreWalletModal(): JSX.Element | null {
const { t } = useTranslation() const { t } = useTranslation()
const colors = useSporeColors() const colors = useSporeColors()
const dispatch = useDispatch() const dispatch = useDispatch()
const { onClose } = useReactNavigationModal()
const restoreType = route.params?.restoreType ?? WalletRestoreType.None const { onClose } = useReactNavigationModal()
const { title, description, isDismissible } = useMemo(() => {
switch (restoreType) {
case WalletRestoreType.SeedPhrase:
return {
title: t('account.wallet.restore.seed_phrase.title'),
description: t('account.wallet.restore.seed_phrase.description'),
isDismissible: true,
}
case WalletRestoreType.NewDevice:
return {
title: t('account.wallet.restore.new_device.title'),
description: t('account.wallet.restore.new_device.description'),
isDismissible: false,
}
default:
return {}
}
}, [restoreType, t])
const onRestore = (): void => { const onRestore = (): void => {
onClose() onClose()
dispatch(closeAllModals()) // still need this until all modals are migrated to react-navigation dispatch(closeAllModals()) // still need this until all modals are migrated to react-navigation
navigate(MobileScreens.OnboardingStack, {
switch (restoreType) { screen: OnboardingScreens.RestoreCloudBackupLoading,
case WalletRestoreType.SeedPhrase: { params: {
navigate(MobileScreens.OnboardingStack, { entryPoint: OnboardingEntryPoint.Sidebar,
screen: OnboardingScreens.RestoreMethod, importType: ImportType.RestoreMnemonic,
params: { },
entryPoint: OnboardingEntryPoint.Sidebar, })
importType: ImportType.RestoreMnemonic,
},
})
break
}
case WalletRestoreType.NewDevice: {
navigate(MobileScreens.OnboardingStack, {
screen: OnboardingScreens.RestoreCloudBackupLoading,
params: {
entryPoint: OnboardingEntryPoint.Sidebar,
importType: ImportType.RestoreMnemonic,
},
})
break
}
}
} }
return ( return (
<Modal <Modal hideHandlebar backgroundColor={colors.surface1.val} isDismissible={false} name={ModalName.RestoreWallet}>
hideHandlebar
backgroundColor={colors.surface1.val}
isDismissible={isDismissible}
name={ModalName.RestoreWallet}
onClose={onClose}
>
<Flex centered gap="$spacing24" px="$spacing24" py="$spacing12" backgroundColor="$surface1"> <Flex centered gap="$spacing24" px="$spacing24" py="$spacing12" backgroundColor="$surface1">
<Flex <Flex
centered centered
...@@ -119,43 +87,23 @@ export function RestoreWalletModal({ route }: AppStackScreenProp<typeof ModalNam ...@@ -119,43 +87,23 @@ export function RestoreWalletModal({ route }: AppStackScreenProp<typeof ModalNam
> >
<WalletFilled color="$neutral1" size="$icon.24" /> <WalletFilled color="$neutral1" size="$icon.24" />
<Flex position="absolute" bottom={ICON_OFFSET} right={ICON_OFFSET}> <Flex position="absolute" bottom={ICON_OFFSET} right={ICON_OFFSET}>
<ArrowDownCircleFilledWithBorder color="$accent1" size="$icon.24" /> <ArrowDownCircle color="$accent1" size="$icon.24" />
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>
<GenericHeader title={title} titleVariant="subheading1" subtitle={description} subtitleVariant="body3" /> <GenericHeader
<Flex gap="$spacing8" width="100%"> title={t('account.wallet.button.restore')}
<Flex row> titleVariant="subheading1"
<Button testID={TestID.Continue} variant="branded" emphasis="primary" size="medium" onPress={onRestore}> subtitle={t('account.wallet.restore.description')}
{t('common.button.continue')} subtitleVariant="body3"
</Button> />
</Flex> <Flex row>
{isDismissible && ( <Button variant="branded" emphasis="primary" size="large" onPress={onRestore}>
<Flex row> {t('common.button.continue')}
<Button testID={TestID.Cancel} variant="default" emphasis="secondary" size="medium" onPress={onClose}> </Button>
{t('common.button.notNow')}
</Button>
</Flex>
)}
</Flex> </Flex>
</Flex> </Flex>
</Modal> </Modal>
) )
} }
function BackgroundRing({ size }: { size: number }): JSX.Element {
return (
<Flex
position="absolute"
borderRadius="$roundedFull"
borderColor="$surface3"
borderWidth="$spacing1"
height={size}
width={size}
top="50%"
left="50%"
transform={[{ translateX: -size / 2 }, { translateY: -size / 2 }]}
/>
)
}
/**
* If the wallet needs to be restored such as migrating to a new device,
* this enum describes the type of restore that is needed.
*/
export enum WalletRestoreType {
None = 'none',
/**
* The wallet needs to be restored because it is a new device. This case is
* when the local app state has been restored but the native private keys and
* seed phrase are not present.
*/
NewDevice = 'device',
/**
* The wallet needs to be restored because the seed phrase is not present. This case
* is when the local app state is using a wallet but it's seed phrase is missing.
*/
SeedPhrase = 'seedPhrase',
}
export interface RestoreWalletModalState {
restoreType: WalletRestoreType
}
import { default as React, useCallback } from 'react' import { Action } from '@reduxjs/toolkit'
import { default as React } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal' import { closeModal } from 'src/features/modals/modalSlice'
import { Flex, GeneratedIcon, Text, TouchableArea } from 'ui/src' import { Flex, GeneratedIcon, Text, TouchableArea } from 'ui/src'
import { Check, Contrast, Moon, Sun } from 'ui/src/components/icons' import { Check, Contrast, Moon, Sun } from 'ui/src/components/icons'
import { Modal } from 'uniswap/src/components/modals/Modal' import { Modal } from 'uniswap/src/components/modals/Modal'
...@@ -12,10 +13,13 @@ import { AppearanceSettingType, setSelectedAppearanceSettings } from 'wallet/src ...@@ -12,10 +13,13 @@ import { AppearanceSettingType, setSelectedAppearanceSettings } from 'wallet/src
export function SettingsAppearanceModal(): JSX.Element { export function SettingsAppearanceModal(): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const currentTheme = useCurrentAppearanceSetting() const currentTheme = useCurrentAppearanceSetting()
const { onClose } = useReactNavigationModal() const dispatch = useDispatch()
return ( return (
<Modal name={ModalName.SettingsAppearance} onClose={onClose}> <Modal
name={ModalName.SettingsAppearance}
onClose={(): Action => dispatch(closeModal({ name: ModalName.SettingsAppearance }))}
>
<Flex animation="fast" gap="$spacing16" pb="$spacing24" px="$spacing24" width="100%"> <Flex animation="fast" gap="$spacing16" pb="$spacing24" px="$spacing24" width="100%">
<Flex centered> <Flex centered>
<Text color="$neutral1" variant="subheading1"> <Text color="$neutral1" variant="subheading1">
...@@ -29,7 +33,6 @@ export function SettingsAppearanceModal(): JSX.Element { ...@@ -29,7 +33,6 @@ export function SettingsAppearanceModal(): JSX.Element {
option={AppearanceSettingType.System} option={AppearanceSettingType.System}
subtitle={t('settings.setting.appearance.option.device.subtitle')} subtitle={t('settings.setting.appearance.option.device.subtitle')}
title={t('settings.setting.appearance.option.device.title')} title={t('settings.setting.appearance.option.device.title')}
onClose={onClose}
/> />
<AppearanceOption <AppearanceOption
Icon={Sun} Icon={Sun}
...@@ -37,7 +40,6 @@ export function SettingsAppearanceModal(): JSX.Element { ...@@ -37,7 +40,6 @@ export function SettingsAppearanceModal(): JSX.Element {
option={AppearanceSettingType.Light} option={AppearanceSettingType.Light}
subtitle={t('settings.setting.appearance.option.light.subtitle')} subtitle={t('settings.setting.appearance.option.light.subtitle')}
title={t('settings.setting.appearance.option.light.title')} title={t('settings.setting.appearance.option.light.title')}
onClose={onClose}
/> />
<AppearanceOption <AppearanceOption
Icon={Moon} Icon={Moon}
...@@ -45,7 +47,6 @@ export function SettingsAppearanceModal(): JSX.Element { ...@@ -45,7 +47,6 @@ export function SettingsAppearanceModal(): JSX.Element {
option={AppearanceSettingType.Dark} option={AppearanceSettingType.Dark}
subtitle={t('settings.setting.appearance.option.dark.subtitle')} subtitle={t('settings.setting.appearance.option.dark.subtitle')}
title={t('settings.setting.appearance.option.dark.title')} title={t('settings.setting.appearance.option.dark.title')}
onClose={onClose}
/> />
</Flex> </Flex>
</Flex> </Flex>
...@@ -59,26 +60,20 @@ interface AppearanceOptionProps { ...@@ -59,26 +60,20 @@ interface AppearanceOptionProps {
subtitle: string subtitle: string
option: AppearanceSettingType option: AppearanceSettingType
Icon: GeneratedIcon Icon: GeneratedIcon
onClose: () => void
} }
function AppearanceOption({ active, title, subtitle, Icon, option, onClose }: AppearanceOptionProps): JSX.Element { function AppearanceOption({ active, title, subtitle, Icon, option }: AppearanceOptionProps): JSX.Element {
const dispatch = useDispatch() const dispatch = useDispatch()
const showCheckMarkOpacity = active ? 1 : 0 const showCheckMarkOpacity = active ? 1 : 0
const changeTheme = useCallback(async (): Promise<void> => {
dispatch(setSelectedAppearanceSettings(option))
onClose()
}, [dispatch, option, onClose])
return ( return (
<TouchableArea <TouchableArea
alignItems="center" alignItems="center"
flexDirection="row" flexDirection="row"
justifyContent="space-between" justifyContent="space-between"
py="$spacing12" py="$spacing12"
onPress={changeTheme} onPress={(): Action => dispatch(setSelectedAppearanceSettings(option))}
> >
<Icon color="$neutral2" size="$icon.24" strokeWidth={1.5} /> <Icon color="$neutral2" size="$icon.24" strokeWidth={1.5} />
<Flex row shrink> <Flex row shrink>
......
import { NavigatorScreenParams, useNavigation } from '@react-navigation/native' import { NavigatorScreenParams, useNavigation } from '@react-navigation/native'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
import { ValueOf } from 'react-native-gesture-handler/lib/typescript/typeUtils' import { ValueOf } from 'react-native-gesture-handler/lib/typescript/typeUtils'
import { useDispatch } from 'react-redux'
import { navigate } from 'src/app/navigation/rootNavigation' import { navigate } from 'src/app/navigation/rootNavigation'
import { import {
AppStackNavigationProp, AppStackNavigationProp,
...@@ -11,15 +12,16 @@ import { ...@@ -11,15 +12,16 @@ import {
} from 'src/app/navigation/types' } from 'src/app/navigation/types'
import { ConnectionsDappsListModalState } from 'src/components/Settings/ConnectionsDappModal/ConnectionsDappsListModalState' import { ConnectionsDappsListModalState } from 'src/components/Settings/ConnectionsDappModal/ConnectionsDappsListModalState'
import { EditWalletSettingsModalState } from 'src/components/Settings/EditWalletModal/EditWalletSettingsModalState' import { EditWalletSettingsModalState } from 'src/components/Settings/EditWalletModal/EditWalletSettingsModalState'
import { openModal } from 'src/features/modals/modalSlice'
import { useIsScreenNavigationReady } from 'src/utils/useIsScreenNavigationReady' import { useIsScreenNavigationReady } from 'src/utils/useIsScreenNavigationReady'
import { Flex, Skeleton, Switch, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Skeleton, Switch, Text, TouchableArea, useSporeColors } from 'ui/src'
import { Arrow } from 'ui/src/components/arrow/Arrow' import { Arrow } from 'ui/src/components/arrow/Arrow'
import { RotatableChevron } from 'ui/src/components/icons' import { RotatableChevron } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { SmartWalletAdvancedSettingsModalState } from 'uniswap/src/features/smartWallet/modals/SmartWalletAdvancedSettingsModal'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { MobileScreens } from 'uniswap/src/types/screens/mobile'
import { openUri } from 'uniswap/src/utils/linking' import { openUri } from 'uniswap/src/utils/linking'
import { SmartWalletAdvancedSettingsModalState } from 'wallet/src/features/smartWallet/modals/SmartWalletAdvancedSettingsModal'
export const SETTINGS_ROW_HEIGHT = 60 export const SETTINGS_ROW_HEIGHT = 60
...@@ -34,6 +36,12 @@ export interface SettingsSectionItemComponent { ...@@ -34,6 +36,12 @@ export interface SettingsSectionItemComponent {
isHidden?: boolean isHidden?: boolean
} }
type SettingsModal =
| typeof ModalName.LanguageSelector
| typeof ModalName.SettingsAppearance
| typeof ModalName.PortfolioBalanceModal
| typeof ModalName.PermissionsModal
type SettingsNavigationModal = type SettingsNavigationModal =
| typeof ModalName.BiometricsModal | typeof ModalName.BiometricsModal
| typeof ModalName.FiatCurrencySelector | typeof ModalName.FiatCurrencySelector
...@@ -42,16 +50,11 @@ type SettingsNavigationModal = ...@@ -42,16 +50,11 @@ type SettingsNavigationModal =
| typeof ModalName.ConnectionsDappListModal | typeof ModalName.ConnectionsDappListModal
| typeof ModalName.SmartWalletAdvancedSettingsModal | typeof ModalName.SmartWalletAdvancedSettingsModal
| typeof ModalName.PasskeyManagement | typeof ModalName.PasskeyManagement
| typeof ModalName.Experiments
| typeof ModalName.SettingsAppearance
| typeof ModalName.PermissionsModal
| typeof ModalName.PortfolioBalanceModal
| typeof ModalName.LanguageSelector
export interface SettingsSectionItem { export interface SettingsSectionItem {
screen?: keyof SettingsStackParamList | typeof MobileScreens.OnboardingStack screen?: keyof SettingsStackParamList | typeof MobileScreens.OnboardingStack
modal?: SettingsModal
navigationModal?: SettingsNavigationModal navigationModal?: SettingsNavigationModal
testID?: string
screenProps?: ValueOf<SettingsStackParamList> | NavigatorScreenParams<OnboardingStackParamList> screenProps?: ValueOf<SettingsStackParamList> | NavigatorScreenParams<OnboardingStackParamList>
navigationProps?: navigationProps?:
| ConnectionsDappsListModalState | ConnectionsDappsListModalState
...@@ -75,13 +78,13 @@ interface SettingsRowProps { ...@@ -75,13 +78,13 @@ interface SettingsRowProps {
page: SettingsSectionItem page: SettingsSectionItem
navigation: SettingsStackNavigationProp & OnboardingStackNavigationProp navigation: SettingsStackNavigationProp & OnboardingStackNavigationProp
checkIfCanProceed?: SettingsSectionItem['checkIfCanProceed'] checkIfCanProceed?: SettingsSectionItem['checkIfCanProceed']
testID?: string
} }
export const SettingsRow = memo( export const SettingsRow = memo(
({ ({
page: { page: {
screen, screen,
modal,
navigationModal, navigationModal,
screenProps, screenProps,
navigationProps, navigationProps,
...@@ -95,12 +98,12 @@ export const SettingsRow = memo( ...@@ -95,12 +98,12 @@ export const SettingsRow = memo(
onToggle, onToggle,
isToggleEnabled, isToggleEnabled,
count, count,
testID,
}, },
navigation, navigation,
checkIfCanProceed, checkIfCanProceed,
}: SettingsRowProps): JSX.Element => { }: SettingsRowProps): JSX.Element => {
const colors = useSporeColors() const colors = useSporeColors()
const dispatch = useDispatch()
const handleRow = useCallback(async (): Promise<void> => { const handleRow = useCallback(async (): Promise<void> => {
if (checkIfCanProceed && !checkIfCanProceed()) { if (checkIfCanProceed && !checkIfCanProceed()) {
...@@ -111,15 +114,28 @@ export const SettingsRow = memo( ...@@ -111,15 +114,28 @@ export const SettingsRow = memo(
return return
} else if (screen) { } else if (screen) {
navigation.navigate(screen, screenProps) navigation.navigate(screen, screenProps)
} else if (modal) {
dispatch(openModal({ name: modal }))
} else if (navigationModal) { } else if (navigationModal) {
navigate(navigationModal, navigationProps) navigate(navigationModal, navigationProps)
} else if (externalLink) { } else if (externalLink) {
await openUri(externalLink) await openUri(externalLink)
} }
}, [checkIfCanProceed, onToggle, screen, navigation, screenProps, navigationProps, navigationModal, externalLink]) }, [
checkIfCanProceed,
onToggle,
screen,
navigation,
screenProps,
navigationProps,
modal,
navigationModal,
dispatch,
externalLink,
])
return ( return (
<TouchableArea disabled={Boolean(action)} testID={testID} onPress={handleRow}> <TouchableArea disabled={Boolean(action)} onPress={handleRow}>
<Flex grow row alignItems="center" gap="$spacing12" minHeight={40}> <Flex grow row alignItems="center" gap="$spacing12" minHeight={40}>
<Flex grow row alignItems={subText ? 'flex-start' : 'center'} flexBasis={0} gap="$spacing12"> <Flex grow row alignItems={subText ? 'flex-start' : 'center'} flexBasis={0} gap="$spacing12">
<Flex centered height={32} width={32}> <Flex centered height={32} width={32}>
...@@ -141,6 +157,7 @@ export const SettingsRow = memo( ...@@ -141,6 +157,7 @@ export const SettingsRow = memo(
)} )}
<RowRightContent <RowRightContent
screen={screen} screen={screen}
modal={modal}
navigationModal={navigationModal} navigationModal={navigationModal}
externalLink={externalLink} externalLink={externalLink}
disabled={disabled} disabled={disabled}
...@@ -176,6 +193,7 @@ const LOADING_DIMENSIONS = { ...@@ -176,6 +193,7 @@ const LOADING_DIMENSIONS = {
const RowRightContent = memo( const RowRightContent = memo(
({ ({
screen, screen,
modal,
navigationModal, navigationModal,
externalLink, externalLink,
disabled, disabled,
...@@ -187,6 +205,7 @@ const RowRightContent = memo( ...@@ -187,6 +205,7 @@ const RowRightContent = memo(
}: Pick< }: Pick<
SettingsSectionItem, SettingsSectionItem,
| 'screen' | 'screen'
| 'modal'
| 'navigationModal' | 'navigationModal'
| 'externalLink' | 'externalLink'
| 'disabled' | 'disabled'
...@@ -217,7 +236,7 @@ const RowRightContent = memo( ...@@ -217,7 +236,7 @@ const RowRightContent = memo(
) )
} }
if (screen || navigationModal) { if (screen || modal || navigationModal) {
return ( return (
<Flex centered row> <Flex centered row>
{currentSetting && {currentSetting &&
......
...@@ -14,32 +14,25 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -14,32 +14,25 @@ exports[`AccountCardItem renders correctly 1`] = `
onPress={[Function]} onPress={[Function]}
> >
<View <View
collapsable={false} hitSlop={
focusVisibleStyle={{}}
forwardedRef={[Function]}
jestAnimatedStyle={
{ {
"value": {}, "bottom": 5,
"left": 5,
"right": 5,
"top": 5,
} }
} }
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]} onResponderGrant={[Function]}
onResponderMove={[Function]} onResponderMove={[Function]}
onResponderRelease={[Function]} onResponderRelease={[Function]}
onResponderTerminate={[Function]} onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]} onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]} onStartShouldSetResponder={[Function]}
role="button"
style={ style={
{ {
"backgroundColor": "transparent",
"borderBottomLeftRadius": 12,
"borderBottomRightRadius": 12,
"borderTopLeftRadius": 12,
"borderTopRightRadius": 12,
"flexDirection": "column", "flexDirection": "column",
"opacity": 1, "opacity": 1,
"paddingBottom": 12, "paddingBottom": 12,
...@@ -55,17 +48,9 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -55,17 +48,9 @@ exports[`AccountCardItem renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "flex-start", "alignItems": "flex-start",
"color": {
"dynamic": {
"dark": "#FFFFFF",
"light": "#222222",
},
},
"flexDirection": "row", "flexDirection": "row",
"gap": 16, "gap": 16,
} }
...@@ -73,8 +58,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -73,8 +58,6 @@ exports[`AccountCardItem renders correctly 1`] = `
testID="account-item/0x82D56A352367453f74FC0dC7B071b311da373Fa6" testID="account-item/0x82D56A352367453f74FC0dC7B071b311da373Fa6"
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flex": 1, "flex": 1,
...@@ -83,8 +66,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -83,8 +66,6 @@ exports[`AccountCardItem renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
onLayout={[Function]} onLayout={[Function]}
style={ style={
{ {
...@@ -96,8 +77,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -96,8 +77,6 @@ exports[`AccountCardItem renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
...@@ -106,8 +85,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -106,8 +85,6 @@ exports[`AccountCardItem renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"backgroundColor": { "backgroundColor": {
...@@ -156,8 +133,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -156,8 +133,6 @@ exports[`AccountCardItem renders correctly 1`] = `
testID="account-icon" testID="account-icon"
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
...@@ -169,8 +144,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -169,8 +144,6 @@ exports[`AccountCardItem renders correctly 1`] = `
</View> </View>
</View> </View>
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
...@@ -180,8 +153,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -180,8 +153,6 @@ exports[`AccountCardItem renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "row", "flexDirection": "row",
...@@ -190,8 +161,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -190,8 +161,6 @@ exports[`AccountCardItem renders correctly 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
...@@ -207,8 +176,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -207,8 +176,6 @@ exports[`AccountCardItem renders correctly 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
maxFontSizeMultiplier={1.4} maxFontSizeMultiplier={1.4}
numberOfLines={1} numberOfLines={1}
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"color": { "color": {
...@@ -237,8 +204,6 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -237,8 +204,6 @@ exports[`AccountCardItem renders correctly 1`] = `
<Text <Text
allowFontScaling={true} allowFontScaling={true}
maxFontSizeMultiplier={1.4} maxFontSizeMultiplier={1.4}
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"color": { "color": {
......
...@@ -86,32 +86,25 @@ exports[`AccountList renders without error 1`] = ` ...@@ -86,32 +86,25 @@ exports[`AccountList renders without error 1`] = `
onPress={[Function]} onPress={[Function]}
> >
<View <View
collapsable={false} hitSlop={
focusVisibleStyle={{}}
forwardedRef={[Function]}
jestAnimatedStyle={
{ {
"value": {}, "bottom": 5,
"left": 5,
"right": 5,
"top": 5,
} }
} }
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
onLayout={[Function]}
onResponderGrant={[Function]} onResponderGrant={[Function]}
onResponderMove={[Function]} onResponderMove={[Function]}
onResponderRelease={[Function]} onResponderRelease={[Function]}
onResponderTerminate={[Function]} onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]} onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]} onStartShouldSetResponder={[Function]}
role="button"
style={ style={
{ {
"backgroundColor": "transparent",
"borderBottomLeftRadius": 12,
"borderBottomRightRadius": 12,
"borderTopLeftRadius": 12,
"borderTopRightRadius": 12,
"flexDirection": "column", "flexDirection": "column",
"opacity": 1, "opacity": 1,
"paddingBottom": 12, "paddingBottom": 12,
...@@ -127,17 +120,9 @@ exports[`AccountList renders without error 1`] = ` ...@@ -127,17 +120,9 @@ exports[`AccountList renders without error 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "flex-start", "alignItems": "flex-start",
"color": {
"dynamic": {
"dark": "#FFFFFF",
"light": "#222222",
},
},
"flexDirection": "row", "flexDirection": "row",
"gap": 16, "gap": 16,
} }
...@@ -145,8 +130,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -145,8 +130,6 @@ exports[`AccountList renders without error 1`] = `
testID="account-item/0x82D56A352367453f74FC0dC7B071b311da373Fa6" testID="account-item/0x82D56A352367453f74FC0dC7B071b311da373Fa6"
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flex": 1, "flex": 1,
...@@ -155,8 +138,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -155,8 +138,6 @@ exports[`AccountList renders without error 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
onLayout={[Function]} onLayout={[Function]}
style={ style={
{ {
...@@ -168,8 +149,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -168,8 +149,6 @@ exports[`AccountList renders without error 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
...@@ -178,8 +157,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -178,8 +157,6 @@ exports[`AccountList renders without error 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"backgroundColor": { "backgroundColor": {
...@@ -228,8 +205,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -228,8 +205,6 @@ exports[`AccountList renders without error 1`] = `
testID="account-icon" testID="account-icon"
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
...@@ -241,8 +216,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -241,8 +216,6 @@ exports[`AccountList renders without error 1`] = `
</View> </View>
</View> </View>
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "column", "flexDirection": "column",
...@@ -252,8 +225,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -252,8 +225,6 @@ exports[`AccountList renders without error 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"flexDirection": "row", "flexDirection": "row",
...@@ -262,8 +233,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -262,8 +233,6 @@ exports[`AccountList renders without error 1`] = `
} }
> >
<View <View
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"alignItems": "center", "alignItems": "center",
...@@ -279,8 +248,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -279,8 +248,6 @@ exports[`AccountList renders without error 1`] = `
ellipsizeMode="tail" ellipsizeMode="tail"
maxFontSizeMultiplier={1.4} maxFontSizeMultiplier={1.4}
numberOfLines={1} numberOfLines={1}
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"color": { "color": {
...@@ -309,8 +276,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -309,8 +276,6 @@ exports[`AccountList renders without error 1`] = `
<Text <Text
allowFontScaling={true} allowFontScaling={true}
maxFontSizeMultiplier={1.4} maxFontSizeMultiplier={1.4}
onBlur={[Function]}
onFocus={[Function]}
style={ style={
{ {
"color": { "color": {
......
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.
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.
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.
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