ci(release): publish latest release

parent db1fbc5e
......@@ -6,28 +6,7 @@ alwaysApply: false
# Mobile Styling Conventions
## Component Styling
- Use the `styled` function from `ui/src` for consistent styling
- Prefer Tamagui styling over React Native StyleSheet when possible
- Use StyleSheet.create for performance-critical styles or when Tamagui isn't suitable
## StyleSheet Usage
- Define styles outside of the component body to prevent recreation on renders
- Use descriptive names for style objects
- Group related styles together
Example:
```typescript
const styles = StyleSheet.create({
container: {
flex: 1,
padding: spacing.spacing16,
},
header: {
fontSize: 18,
fontWeight: 'bold',
},
})
```
- Prefer Tamagui inline props over other methods
## Theme Usage
- Use theme tokens from the UI package instead of hardcoded values
......@@ -40,9 +19,11 @@ const styles = StyleSheet.create({
- Minimize view hierarchy depth
## Platform Specific Code
- Use `$platform-web` for platform-specific styling in Tamagui
- Use `.ios.tsx` and `.android.tsx` extensions for platform-specific components
- Use the `Platform.select` API for inline platform-specific code
- Use `.<platform>.tsx` extensions for platform-specific components
- The bundler will grab the appropriate file during the build and always fallback to `.tsx`
- The `platform` variable must be one of the following: ios, android, macos, windows, web, native
- Use the `Platform.select` API for inline platform-specific code. This method expects an object keyed by `platform`.
- Also consider using our custom platform variables like `isMobileApp`, `isInterface`, etc. for more specific platform detection needs.
## Performance
- Memoize complex style calculations
......
---
description: Component Structure and Best Practices
globs:
alwaysApply: false
description:
globs:
alwaysApply: true
---
# Component Structure and Best Practices
## Component Organization
- Place state and hooks at the top of the component
- Group related state variables together
- Extract complex logic into custom hooks
- Define handlers after state declarations
- Place JSX return statement at the end of the component
## Props
- Use interface for component props
- Place prop interface directly above component
- Complex or shared types can be moved to a types.ts file
- Use descriptive prop names
- Provide default props where appropriate
## Performance Optimizations
- Memoize expensive calculations with useMemo
- Memoize event handlers with useCallback or use our custom useEvent hook
- Use React.memo for pure components that render often
- Avoid anonymous functions in render
## Component Size
- Keep components focused on a single responsibility
- Extract complex components into smaller, reusable pieces
- Aim for less than 250 lines per component file
- Extract prop interfaces and types to separate files if they become complex
## Component Structure Example
```typescript
interface ExampleComponentProps {
prop1: string;
prop2: () => void;
}
export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.Element {
// State declarations
const [state1, setState1] = useState(false)
const [state2, setState2] = useState<string>('')
// Custom hooks
const { data, loading } = useCustomHook()
// Queries and mutations
const { data, isPending } = useQuery(exampleQueries.getData(prop1))
const mutation = useMutation({
mutationFn: () => exampleService.submit(prop1),
onSuccess: prop2
})
// Derived values
const derivedValue = useMemo(() => {
return someCalculation(state1, prop1)
}, [state1, prop1])
return someCalculation(state1, data)
}, [state1, data])
// Event handlers
const handleClick = useCallback(() => {
setState1(!state1)
}, [state1])
mutation.mutate()
}, [state1, mutation])
// Side effects
useEffect(() => {
......@@ -38,7 +67,7 @@ export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.E
}, [prop2])
// Conditional rendering logic
if (loading) {
if (isPending) {
return <LoadingSpinner />
}
......@@ -51,21 +80,3 @@ export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.E
)
}
```
## Props
- Use interface for component props
- Place prop interface directly above component or in a types.ts file
- Use descriptive prop names
- Provide default props where appropriate
## Performance Optimizations
- Memoize expensive calculations with useMemo
- Memoize event handlers with useCallback
- Use React.memo for pure components that render often
- Avoid anonymous functions in render
## Component Size
- Keep components focused on a single responsibility
- Extract complex components into smaller, reusable pieces
- Aim for less than 250 lines per component file
- Extract prop interfaces and types to separate files if they become complex
---
description:
globs:
alwaysApply: true
---
## Data Fetching Patterns
### Direct Query Usage (Preferred)
- Use direct `useQuery` and `useMutation` calls in components instead of wrapping them in custom hooks
- Place query/mutation calls at the top of the component with other hooks
- Use query factories from domain-specific files
```typescript
// ❌ Avoid: Unnecessary custom hook
function useTokenPrice(tokenAddress) {
const { data, isLoading } = useQuery(tokenQueries.price(tokenAddress));
return { data, isLoading }
}
// ✅ Preferred: Direct usage
function TokenPriceDisplay({ tokenAddress }) {
const { data, isLoading } = useQuery(tokenQueries.price(tokenAddress));
// ...
}
```
### Custom Hooks (Limited Cases)
Only create custom hooks when:
1. Combining multiple related queries
2. Implementing complex business logic beyond data fetching
3. Managing domain-specific operations that transcend data fetching
```typescript
// ✅ Valid: Complex business logic
function useUserPermissions() {
const { data: user } = useQuery(userQueries.current());
const { data: roles } = useQuery(roleQueries.forUser(user?.id));
const canEditProducts = useMemo(() => {
if (!user || !roles) return false;
return roles.some(r => r.permissions.includes('product:edit')) ||
user.isAdmin;
}, [user, roles]);
return { canEditProducts };
}
```
......@@ -6,6 +6,7 @@ ignores: [
'i18next',
'moti',
'wrangler',
'react-router',
# Dependencies that depcheck thinks are missing but are actually present or never used
'@yarnpkg/core',
'@yarnpkg/cli',
......
......@@ -69,3 +69,4 @@ CLAUDE.local.md
# cursor
.cursor/mcp.json
.cursor/rules/local-workflow.mdc
diff --git a/registry.js b/registry.js
index 64b2735d3bb5284bd2450bf0d06115c3de5dcf80..489ffa2f59a1d08d71826e2ceb0076d588636c7c 100644
--- a/registry.js
+++ b/registry.js
@@ -8,8 +8,9 @@
* @format
*/
-'use strict';
+"use strict";
+/*::
export type AssetDestPathResolver = 'android' | 'generic';
export type PackagerAsset = {
@@ -25,17 +26,18 @@ export type PackagerAsset = {
+resolver?: AssetDestPathResolver,
...
};
+*/
-const assets: Array<PackagerAsset> = [];
+const assets/*::: Array<PackagerAsset>*/ = [];
-function registerAsset(asset: PackagerAsset): number {
+function registerAsset(asset/*::: PackagerAsset*/)/*::: number*/ {
// `push` returns new array length, so the first asset will
// get id 1 (not 0) to make the value truthy
return assets.push(asset);
}
-function getAssetByID(assetId: number): PackagerAsset {
+function getAssetByID(assetId/*::: number*/)/*::: PackagerAsset*/ {
return assets[assetId - 1];
}
-module.exports = {registerAsset, getAssetByID};
+module.exports = { registerAsset, getAssetByID };
diff --git a/settings-plugin/src/main/kotlin/com/facebook/react/ReactSettingsExtension.kt b/settings-plugin/src/main/kotlin/com/facebook/react/ReactSettingsExtension.kt
index aea525747e08b811eabae78c4be486fe7b3c46ba..a70b9cd72346487d347337cc95c24a4a3d8652f8 100644
--- a/settings-plugin/src/main/kotlin/com/facebook/react/ReactSettingsExtension.kt
+++ b/settings-plugin/src/main/kotlin/com/facebook/react/ReactSettingsExtension.kt
@@ -29,7 +29,7 @@ abstract class ReactSettingsExtension @Inject constructor(val settings: Settings
settings.layout.rootDirectory.file("build/generated/autolinking/").asFile
private val defaultConfigCommand: List<String> =
- windowsAwareCommandLine(listOf("npx", "@react-native-community/cli", "config")).map {
+ windowsAwareCommandLine(listOf("node", "../../node_modules/@react-native-community/cli/build/bin.js", "config")).map {
it.toString()
}
diff --git a/RNFastImage.podspec b/RNFastImage.podspec
index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612c682ae0b 100644
index db0fada63fc06191f8620d336d244edde6c3dba3..93d7291b183b9625ad8d50e812ae247a11bad9d3 100644
--- a/RNFastImage.podspec
+++ b/RNFastImage.podspec
@@ -16,6 +16,6 @@ Pod::Spec.new do |s|
......@@ -7,6 +7,7 @@ index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612
s.dependency 'React-Core'
- s.dependency 'SDWebImage', '~> 5.11.1'
+ s.dependency 'SDWebImage', '~> 5.15.5'
s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
- s.dependency 'SDWebImageWebPCoder', '~> 0.8.4'
+ s.dependency 'SDWebImage', '~> 5.21.0'
+ s.dependency 'SDWebImageWebPCoder', '~> 0.14.6'
end
IPFS hash of the deployment:
- CIDv0: `QmSighTNwdbMwNVFqAKsFqh2CRmBQ6ZwqVF52RRwHa19gG`
- CIDv1: `bafybeicbcpddgur5bzzdq2tlgvnf6t3l4fzi74tfuo5b2luhk2bku3f2su`
- CIDv0: `QmR1FhjPj6guoB8B8Qq5g3H6MrcfPXPeM8GsTBQZprHsxQ`
- CIDv1: `bafybeibhtmbqzvasqpaerlcxgwm5gxaomyjsrq3fxs5mg564vmtgcweqau`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
......@@ -10,14 +10,63 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeicbcpddgur5bzzdq2tlgvnf6t3l4fzi74tfuo5b2luhk2bku3f2su.ipfs.dweb.link/
- [ipfs://QmSighTNwdbMwNVFqAKsFqh2CRmBQ6ZwqVF52RRwHa19gG/](ipfs://QmSighTNwdbMwNVFqAKsFqh2CRmBQ6ZwqVF52RRwHa19gG/)
- https://bafybeibhtmbqzvasqpaerlcxgwm5gxaomyjsrq3fxs5mg564vmtgcweqau.ipfs.dweb.link/
- [ipfs://QmR1FhjPj6guoB8B8Qq5g3H6MrcfPXPeM8GsTBQZprHsxQ/](ipfs://QmR1FhjPj6guoB8B8Qq5g3H6MrcfPXPeM8GsTBQZprHsxQ/)
### 5.102.2 (2025-07-08)
## 5.103.0 (2025-07-09)
### Features
* **web:** sol chain id (#20695) 8cff970
* **web:** SolanaWalletProvider (#21164) 96864e0
* **web:** WalletConnector Meta and Services (#21165) 300d887
### Bug Fixes
* **web:** prod cherry pick swap confirmed events (#21566) 8975015
* **web:** approval for v2 migrations (#21396) 813c1de
* **web:** auto-checksum LP creation hook addresses (#21358) cfd5f31
* **web:** Check for undefined approval amount - staging (#21530) e895410
* **web:** fix bug with creating a new custom in range position for v3 (#21560) f763021
* **web:** fix button layout on mweb new position screen (#21307) 790baf7
* **web:** fix issue with custom range and creating a pool (#21402) ee04c77
* **web:** fix merging of dynamic and regular fee tiers (#21395) 67cf972
* **web:** force non x quotes for permit2 tests (#21425) 78b8841
* **web:** i18n not working for transactions (#21447) ca940cc
* **web:** language not translating (#21452) 7813f0c
* **web:** mock png asset imports for playwright (#21415) 3f27389
* **web:** overflow text when creating a new LP position in spanish (#21337) 3864dfc
* **web:** prevent incrementing or decrementing past the tick limits (#21441) a775910
* **web:** race condition reverting language change (#21292) 0ced7f1
* **web:** revert scroll lock performance (#21039) (#21414) 1f687a6
* **web:** reward apr not translated (#21450) c237f07
* **web:** transactions table type translations (#21449) c728fb2
* **web:** update decimals for fee tier display (#21365) 9ba591d
* **web:** Warn instead of error for known bug (#21499) c3633e4
### Continuous Integration
* **web:** update sitemaps d1df877
### Tests
* **web:** a few flakey tests (#21427) 05d8da0
* **web:** check if wrap is ready (#21426) 3b8aa86
* **web:** for tests (#21466) 1e54b9e
### Code Refactoring
* **web:** align send transactiontype enum with uniswap transaction type (#21219) 01fa235
* **web:** migrate Bridge enum val (#21061) 474657a
* **web:** migrate PERMIT enum value to match Uniswap's transactionType (#21053) 59f7814
* **web:** migrate SendTransactionInfo to SendTokenTransactionInfo Uniswap type (#21223) 99293a9
* **web:** migrate v3tov4 enum value to uniswap enum (#21109) 01a3b2d
* **web:** move v3tov4 info type to uniswap (#21110) cc29edc
* **web:** update LP Incentives Claim Rewards enum to use Uniswap TransactionType enum (#21225) ce16f52
* **web:** use uniswap BridgeTransactionInfo type (#21217) 6ba5fce
web/5.102.2
\ No newline at end of file
web/5.103.0
\ No newline at end of file
......@@ -31,14 +31,14 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "14.1.0",
"react-native": "0.76.9",
"react-native-gesture-handler": "2.21.2",
"react-native": "0.77.2",
"react-native-gesture-handler": "2.22.1",
"react-native-reanimated": "3.16.7",
"react-native-svg": "15.10.1",
"react-native-svg": "15.11.2",
"react-native-web": "0.19.13",
"react-qr-code": "2.0.12",
"react-redux": "8.0.5",
"react-router-dom": "6.30.1",
"react-router": "7.6.3",
"redux": "4.2.1",
"redux-logger": "3.0.6",
"redux-persist": "6.0.0",
......
import { PropsWithChildren } from 'react'
import { useRouteError } from 'react-router-dom'
import { useRouteError } from 'react-router'
export function ErrorElement({ children }: PropsWithChildren<unknown>): JSX.Element {
const error = useRouteError()
......
......@@ -3,7 +3,7 @@ import 'src/app/Global.css'
import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, order matters
import { useEffect } from 'react'
import { RouteObject, RouterProvider, createHashRouter } from 'react-router-dom'
import { RouteObject, RouterProvider, createHashRouter } from 'react-router'
import { PersistGate } from 'redux-persist/integration/react'
import { ErrorElement } from 'src/app/components/ErrorElement'
import { BaseAppContainer } from 'src/app/core/BaseAppContainer'
......@@ -35,7 +35,6 @@ import { OTPInput } from 'src/app/features/onboarding/scan/OTPInput'
import { ScanToOnboard } from 'src/app/features/onboarding/scan/ScanToOnboard'
import { ScantasticContextProvider } from 'src/app/features/onboarding/scan/ScantasticContextProvider'
import { OnboardingRoutes, TopLevelRoutes } from 'src/app/navigation/constants'
import { ROUTER_FUTURE_FLAGS, ROUTER_PROVIDER_FUTURE_FLAGS } from 'src/app/navigation/routerConfig'
import { setRouter, setRouterState } from 'src/app/navigation/state'
import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { checksIfSupportsSidePanel } from 'src/app/utils/chrome'
......@@ -140,19 +139,14 @@ const allRoutes = [
},
]
const router = createHashRouter(
[
{
path: `/${TopLevelRoutes.Onboarding}`,
element: <OnboardingWrapper />,
errorElement: <ErrorElement />,
children: !supportsSidePanel ? [unsupportedRoute] : allRoutes,
},
],
const router = createHashRouter([
{
future: ROUTER_FUTURE_FLAGS,
path: `/${TopLevelRoutes.Onboarding}`,
element: <OnboardingWrapper />,
errorElement: <ErrorElement />,
children: !supportsSidePanel ? [unsupportedRoute] : allRoutes,
},
)
])
function ScantasticFlow({ isResetting = false }: { isResetting?: boolean }): JSX.Element {
return (
......@@ -199,7 +193,7 @@ export default function OnboardingApp(): JSX.Element {
<PersistGate persistor={getReduxPersistor()}>
<BaseAppContainer appName={DatadogAppNameTag.Onboarding}>
<PrimaryAppInstanceDebuggerLazy />
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} />
<RouterProvider router={router} />
</BaseAppContainer>
</PersistGate>
)
......
......@@ -3,11 +3,10 @@ import 'src/app/Global.css'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { RouterProvider, createHashRouter } from 'react-router-dom'
import { RouterProvider, createHashRouter } from 'react-router'
import { ErrorElement } from 'src/app/components/ErrorElement'
import { BaseAppContainer } from 'src/app/core/BaseAppContainer'
import { DatadogAppNameTag } from 'src/app/datadog'
import { ROUTER_FUTURE_FLAGS, ROUTER_PROVIDER_FUTURE_FLAGS } from 'src/app/navigation/routerConfig'
import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { Button, Flex, Image, Text } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets'
......@@ -18,18 +17,13 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { ExtensionScreens } from 'uniswap/src/types/screens/extension'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
const router = createHashRouter(
[
{
path: '',
element: <PopupContent />,
errorElement: <ErrorElement />,
},
],
const router = createHashRouter([
{
future: ROUTER_FUTURE_FLAGS,
path: '',
element: <PopupContent />,
errorElement: <ErrorElement />,
},
)
])
function PopupContent(): JSX.Element {
const { t } = useTranslation()
......@@ -108,7 +102,7 @@ export default function PopupApp(): JSX.Element {
return (
<BaseAppContainer appName={DatadogAppNameTag.Popup}>
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} />
<RouterProvider router={router} />
</BaseAppContainer>
)
}
......@@ -4,7 +4,7 @@ import 'src/app/Global.css'
import { SharedEventName } from '@uniswap/analytics-events'
import { useEffect, useRef, useState } from 'react'
import { useDispatch } from 'react-redux'
import { RouterProvider, createHashRouter } from 'react-router-dom'
import { RouterProvider, createHashRouter } from 'react-router'
import { PersistGate } from 'redux-persist/integration/react'
import { ErrorElement } from 'src/app/components/ErrorElement'
import { BaseAppContainer } from 'src/app/core/BaseAppContainer'
......@@ -29,7 +29,6 @@ import { SwapFlowScreen } from 'src/app/features/swap/SwapFlowScreen'
import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked'
import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants'
import { MainContent, WebNavigation } from 'src/app/navigation/navigation'
import { ROUTER_FUTURE_FLAGS, ROUTER_PROVIDER_FUTURE_FLAGS } from 'src/app/navigation/routerConfig'
import { setRouter, setRouterState } from 'src/app/navigation/state'
import { initExtensionAnalytics } from 'src/app/utils/analytics'
import {
......@@ -49,93 +48,88 @@ import { useInterval } from 'utilities/src/time/timing'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
import { getReduxPersistor } from 'wallet/src/state/persistor'
const router = createHashRouter(
[
{
path: '',
element: <SidebarWrapper />,
errorElement: <ErrorElement />,
children: [
{
path: '',
element: <MainContent />,
},
{
path: AppRoutes.AccountSwitcher,
element: <AccountSwitcherScreen />,
},
{
path: AppRoutes.Settings,
element: <SettingsScreenWrapper />,
children: [
{
path: '',
element: <SettingsScreen />,
},
{
path: SettingsRoutes.ChangePassword,
element: <SettingsChangePasswordScreen />,
},
{
path: SettingsRoutes.DeviceAccess,
element: <DeviceAccessScreen />,
},
isDevEnv()
? {
path: SettingsRoutes.DevMenu,
element: <DevMenuScreen />,
}
: {},
{
path: SettingsRoutes.ViewRecoveryPhrase,
element: <ViewRecoveryPhraseScreen />,
},
{
path: SettingsRoutes.BackupRecoveryPhrase,
element: <BackupRecoveryPhraseScreen />,
},
{
path: SettingsRoutes.RemoveRecoveryPhrase,
children: [
{
path: RemoveRecoveryPhraseRoutes.Wallets,
element: <RemoveRecoveryPhraseWallets />,
},
{
path: RemoveRecoveryPhraseRoutes.Verify,
element: <RemoveRecoveryPhraseVerify />,
},
],
},
{
path: SettingsRoutes.ManageConnections,
element: <SettingsManageConnectionsScreen />,
},
{
path: SettingsRoutes.SmartWallet,
element: <SmartWalletSettingsScreen />,
},
],
},
{
path: AppRoutes.Send,
element: <SendFlow />,
},
{
path: AppRoutes.Swap,
element: <SwapFlowScreen />,
},
{
path: AppRoutes.Receive,
element: <ReceiveScreen />,
},
],
},
],
const router = createHashRouter([
{
future: ROUTER_FUTURE_FLAGS,
path: '',
element: <SidebarWrapper />,
errorElement: <ErrorElement />,
children: [
{
path: '',
element: <MainContent />,
},
{
path: AppRoutes.AccountSwitcher,
element: <AccountSwitcherScreen />,
},
{
path: AppRoutes.Settings,
element: <SettingsScreenWrapper />,
children: [
{
path: '',
element: <SettingsScreen />,
},
{
path: SettingsRoutes.ChangePassword,
element: <SettingsChangePasswordScreen />,
},
{
path: SettingsRoutes.DeviceAccess,
element: <DeviceAccessScreen />,
},
isDevEnv()
? {
path: SettingsRoutes.DevMenu,
element: <DevMenuScreen />,
}
: {},
{
path: SettingsRoutes.ViewRecoveryPhrase,
element: <ViewRecoveryPhraseScreen />,
},
{
path: SettingsRoutes.BackupRecoveryPhrase,
element: <BackupRecoveryPhraseScreen />,
},
{
path: SettingsRoutes.RemoveRecoveryPhrase,
children: [
{
path: RemoveRecoveryPhraseRoutes.Wallets,
element: <RemoveRecoveryPhraseWallets />,
},
{
path: RemoveRecoveryPhraseRoutes.Verify,
element: <RemoveRecoveryPhraseVerify />,
},
],
},
{
path: SettingsRoutes.ManageConnections,
element: <SettingsManageConnectionsScreen />,
},
{
path: SettingsRoutes.SmartWallet,
element: <SmartWalletSettingsScreen />,
},
],
},
{
path: AppRoutes.Send,
element: <SendFlow />,
},
{
path: AppRoutes.Swap,
element: <SwapFlowScreen />,
},
{
path: AppRoutes.Receive,
element: <ReceiveScreen />,
},
],
},
)
])
const PORT_PING_INTERVAL = 5 * ONE_SECOND_MS
function useDappRequestPortListener(): void {
......@@ -252,7 +246,7 @@ export default function SidebarApp(): JSX.Element {
<BaseAppContainer appName={DatadogAppNameTag.Sidebar}>
<DappContextProvider>
<PrimaryAppInstanceDebuggerLazy />
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} />
<RouterProvider router={router} />
</DappContextProvider>
</BaseAppContainer>
</PersistGate>
......
......@@ -2,7 +2,7 @@ import '@tamagui/core/reset.css'
import 'src/app/Global.css'
import { PropsWithChildren, useEffect } from 'react'
import { Outlet, RouterProvider, createHashRouter, useSearchParams } from 'react-router-dom'
import { Outlet, RouterProvider, createHashRouter, useSearchParams } from 'react-router'
import { ErrorElement } from 'src/app/components/ErrorElement'
import { BaseAppContainer } from 'src/app/core/BaseAppContainer'
import { DatadogAppNameTag } from 'src/app/datadog'
......@@ -19,7 +19,6 @@ import { UnitagConfirmationScreen } from 'src/app/features/unitags/UnitagConfirm
import { UnitagCreateUsernameScreen } from 'src/app/features/unitags/UnitagCreateUsernameScreen'
import { UnitagIntroScreen } from 'src/app/features/unitags/UnitagIntroScreen'
import { UnitagClaimRoutes } from 'src/app/navigation/constants'
import { ROUTER_FUTURE_FLAGS, ROUTER_PROVIDER_FUTURE_FLAGS } from 'src/app/navigation/routerConfig'
import { setRouter, setRouterState } from 'src/app/navigation/state'
import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { Flex } from 'ui/src'
......@@ -28,29 +27,24 @@ import { usePrevious } from 'utilities/src/react/hooks'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks'
const router = createHashRouter(
[
{
path: '',
element: <UnitagAppInner />,
children: [
{
path: UnitagClaimRoutes.ClaimIntro,
element: <UnitagClaimFlow />,
errorElement: <ErrorElement />,
},
{
path: UnitagClaimRoutes.EditProfile,
element: <UnitagEditProfileFlow />,
errorElement: <ErrorElement />,
},
],
},
],
const router = createHashRouter([
{
future: ROUTER_FUTURE_FLAGS,
path: '',
element: <UnitagAppInner />,
children: [
{
path: UnitagClaimRoutes.ClaimIntro,
element: <UnitagClaimFlow />,
errorElement: <ErrorElement />,
},
{
path: UnitagClaimRoutes.EditProfile,
element: <UnitagEditProfileFlow />,
errorElement: <ErrorElement />,
},
],
},
)
])
/**
* Note: we are using a pattern here to avoid circular dependencies, because
......@@ -144,7 +138,7 @@ export default function UnitagClaimApp(): JSX.Element {
return (
<BaseAppContainer appName={DatadogAppNameTag.UnitagClaim}>
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} />
<RouterProvider router={router} />
</BaseAppContainer>
)
}
......@@ -236,7 +236,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
style="display: flex; flex-basis: 0px; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 1; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; align-self: stretch; flex-grow: 1; outline-color: rgba(0, 0, 0, 0); padding: 8px 12px 8px 12px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 8px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _fontSize-f-size-smal108 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1269072494"
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _fontSize-f-size-smal108 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1269072494"
line-height-disabled="false"
>
Edit label
</span>
......@@ -530,7 +531,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = `
style="display: flex; flex-basis: 0px; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 1; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; align-self: stretch; flex-grow: 1; outline-color: rgba(0, 0, 0, 0); padding: 8px 12px 8px 12px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 8px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _fontSize-f-size-smal108 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1269072494"
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _fontSize-f-size-smal108 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1269072494"
line-height-disabled="false"
>
Edit label
</span>
......
......@@ -71,7 +71,7 @@ export function WrapRequestContent({
// Parse the amount from the data field (remove the function selector - first 10 characters including 0x)
const data = dappRequest.transaction.data.toString()
if (data.length > 10) {
amountValue = parseInt(data.slice(10), 16).toString()
amountValue = parseInt(data.slice(10, 74), 16).toString() // for withdraw(uint256), calldata is 36 bytes (4-byte selector + 32-byte argument). 1 byte = 2 hex characters, and data includes the 0x prefix, so select 64 characters starting from 10th character
}
} else {
// For wrap, amount is in the value field
......
......@@ -61,13 +61,9 @@ function SendCallsRequestContent({
export function SendCallsRequestHandler({ request }: { request: DappRequestStoreItemForSendCallsTxn }): JSX.Element {
const { dappUrl, onConfirm, onCancel } = useDappRequestQueueContext()
const chainId = useDappLastChainId(dappUrl)
const chainId = useDappLastChainId(dappUrl) ?? request.dappInfo?.lastChainId
const activeAccountAddress = useActiveAccountAddressWithThrow()
if (!chainId) {
throw new Error('Chain ID is required')
}
const { dappRequest } = request
const parsedSwapCalldata = useMemo(() => {
......@@ -81,11 +77,13 @@ export function SendCallsRequestHandler({ request }: { request: DappRequestStore
const { data: encoded7702data } = useWalletEncode7702Query({
enabled: !!chainId,
params: {
calls: transformCallsToTransactionRequests({
calls: dappRequest.calls,
chainId,
accountAddress: activeAccountAddress,
}),
calls: chainId
? transformCallsToTransactionRequests({
calls: dappRequest.calls,
chainId,
accountAddress: activeAccountAddress,
})
: [],
smartContractDelegationAddress: UNISWAP_DELEGATION_ADDRESS,
// @ts-ignore - walletAddress is needed for the API but not in the type yet
// TODO: remove this once the API is updated
......
/* eslint-disable max-lines */
import { Provider, TransactionResponse } from '@ethersproject/providers'
import { providerErrors, rpcErrors, serializeError } from '@metamask/rpc-errors'
import { createSearchParams } from 'react-router-dom'
import { createSearchParams } from 'react-router'
import { changeChain } from 'src/app/features/dapp/changeChain'
import { DappInfo, dappStore } from 'src/app/features/dapp/store'
import { getActiveConnectedAccount } from 'src/app/features/dapp/utils'
......
......@@ -81,7 +81,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
screen: ExtensionScreens.Home,
element: ElementName.Send,
})
navigate(AppRoutes.Send)
navigate(`/${AppRoutes.Send}`)
}
const onSwapClick = (): void => {
......@@ -89,7 +89,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
screen: ExtensionScreens.Home,
element: ElementName.Swap,
})
navigate(AppRoutes.Swap)
navigate(`/${AppRoutes.Swap}`)
}
const onReceiveClick = (): void => {
......@@ -97,7 +97,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
screen: ExtensionScreens.Home,
element: ElementName.Receive,
})
navigate(AppRoutes.Receive)
navigate(`/${AppRoutes.Receive}`)
}
const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false)
......
......@@ -112,7 +112,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf
const onPressAccount = async (): Promise<void> => {
dispatch(closePopup(PopupName.Connect))
navigate(AppRoutes.AccountSwitcher)
navigate(`/${AppRoutes.AccountSwitcher}`)
}
const { isConnected, lastChainId, dappUrl, dappIconUrl } = useDappContext()
......
......@@ -60,7 +60,7 @@ function TokenBalanceListInner(): JSX.Element {
}
const onPressReceive = (): void => {
navigate(AppRoutes.Receive)
navigate(`/${AppRoutes.Receive}`)
}
const hasData = !!balancesById
......
import { useEffect, useState } from 'react'
import { Outlet } from 'react-router-dom'
import { Outlet } from 'react-router'
import { DevMenuModal } from 'src/app/core/DevMenuModal'
import { StorageWarningModal } from 'src/app/features/warnings/StorageWarningModal'
import { ONBOARDING_BACKGROUND_DARK, ONBOARDING_BACKGROUND_LIGHT } from 'src/assets'
......
import { PropsWithChildren } from 'react'
import { Trans } from 'react-i18next'
import { Link, LinkProps } from 'react-router-dom'
import { Link, LinkProps } from 'react-router'
import { Text } from 'ui/src'
import { uniswapUrls } from 'uniswap/src/constants/urls'
......
import { Action, AuthenticationTypes } from '@uniswap/client-embeddedwallet/dist/uniswap/embeddedwallet/v1/service_pb'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Navigate, useLocation } from 'react-router-dom'
import { Navigate, useLocation } from 'react-router'
import { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { usePasskeyImportContext } from 'src/app/features/onboarding/import/PasskeyImportContextProvider'
import {
......
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom'
import { useLocation } from 'react-router'
import { OptionCard } from 'src/app/components/buttons/OptionCard'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import {
......
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import { useFinishExtensionOnboarding } from 'src/app/features/onboarding/useFinishExtensionOnboarding'
import { terminateStoreSynchronization } from 'src/store/storeSynchronization'
import { Flex, Text } from 'ui/src'
......
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import { useDappContext } from 'src/app/features/dapp/DappContext'
import { removeDappConnection } from 'src/app/features/dapp/actions'
import { SwitchNetworksModal } from 'src/app/features/home/SwitchNetworksModal'
......
......@@ -6015,7 +6015,8 @@ exports[`ReceiveScreen renders without error 1`] = `
style="display: flex; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; outline-color: rgba(0, 0, 0, 0); padding: 4px 6px 4px 6px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 4px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _fontSize-f-size-micr111 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1263414315"
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _fontSize-f-size-micr111 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1263414315"
line-height-disabled="false"
>
Networks
</span>
......@@ -12051,7 +12052,8 @@ exports[`ReceiveScreen renders without error 1`] = `
style="display: flex; flex-basis: auto; box-sizing: border-box; position: relative; min-height: 0px; min-width: 0px; flex-shrink: 0; flex-direction: row; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; align-items: center; justify-content: center; background-color: rgba(34, 34, 34, 0.05); border-top-color: transparent; border-right-color: transparent; border-bottom-color: transparent; border-left-color: transparent; cursor: pointer; height: auto; outline-color: rgba(0, 0, 0, 0); padding: 4px 6px 4px 6px; border-top-left-radius: 12px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; border-bottom-left-radius: 12px; gap: 4px; border-bottom-style: solid; border-top-style: solid; border-left-style: solid; border-right-style: solid; transform: scale(1);"
>
<span
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _fontSize-f-size-micr111 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1263414315"
class="font_button _color-_groupitem-hover_neutral1Hov3121676 _display-inline _boxSizing-border-box _wordWrap-break-word _whiteSpace-nowrap _mt-0px _mr-0px _mb-0px _ml-0px _color-neutral1 _fontFamily-f-family _maxWidth-10037 _overflowX-hidden _overflowY-hidden _textOverflow-ellipsis _textAlign-center _fontSize-f-size-micr111 _fontWeight-f-weight-me3083741 _lineHeight-f-lineHeigh1263414315"
line-height-disabled="false"
>
Networks
</span>
......
import { useTranslation } from 'react-i18next'
import { Button, Flex } from 'ui/src'
import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { isWeb } from 'utilities/src/platform'
import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext'
......@@ -21,7 +21,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem
derivedSendInfo: { chainId },
} = useSendContext()
const nativeCurrencySymbol = NativeCurrency.onChain(chainId).symbol
const nativeCurrencySymbol = nativeOnChain(chainId).symbol
const insufficientGasFunds = warnings.warnings.some((warning) => warning.type === WarningLabel.InsufficientGasFunds)
......@@ -29,7 +29,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem
const buttonText = insufficientGasFunds
? t('send.warning.insufficientFunds.title', {
currencySymbol: nativeCurrencySymbol,
currencySymbol: nativeCurrencySymbol ?? '',
})
: t('common.button.review')
......
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import { Flex, Text } from 'ui/src'
import { uniswapUrls } from 'uniswap/src/constants/urls'
......
......@@ -121,7 +121,7 @@ export function ViewRecoveryPhraseScreen({
emphasis="secondary"
onPress={(): void =>
navigate(
`${AppRoutes.Settings}/${SettingsRoutes.RemoveRecoveryPhrase}/${RemoveRecoveryPhraseRoutes.Wallets}`,
`/${AppRoutes.Settings}/${SettingsRoutes.RemoveRecoveryPhrase}/${RemoveRecoveryPhraseRoutes.Wallets}`,
{ replace: true },
)
}
......
import { Outlet } from 'react-router-dom'
import { Outlet } from 'react-router'
import { Flex } from 'ui/src'
/**
......
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import { ColorTokens, Flex, GeneratedIcon, Text, TouchableArea, useSporeColors } from 'ui/src'
import { RotatableChevron } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
......
......@@ -26,7 +26,7 @@ export function EditUnitagProfileScreen({ enableBack = false }: { enableBack?: b
useEffect(() => {
if (!pending && !fetching && !unitag) {
navigate(UnitagClaimRoutes.ClaimIntro)
navigate(`/${UnitagClaimRoutes.ClaimIntro}`)
}
}, [unitag, pending, fetching])
......
......@@ -21,7 +21,7 @@ export function UnitagIntroScreen(): JSX.Element {
useEffect(() => {
if (unitag?.address) {
navigate(UnitagClaimRoutes.EditProfile)
navigate(`/${UnitagClaimRoutes.EditProfile}`)
}
}, [unitag])
......
import { useEffect, useState } from 'react'
import { createSearchParams } from 'react-router-dom'
import { createSearchParams } from 'react-router'
import { getRouter } from 'src/app/navigation/state'
import { sleep } from 'utilities/src/time/timing'
......@@ -14,7 +14,7 @@ export function useOptimizedSearchParams(): URLSearchParams {
useEffect(() => {
return getRouter().subscribe(async () => {
// react-router-dom calls this before it actually updates the url bar :/
// react-router calls this before it actually updates the url bar :/
await sleep(0)
setSearchParams((prev) => {
const next = getSearchParams()
......
import { PropsWithChildren, useCallback } from 'react'
import { createSearchParams, useNavigate } from 'react-router-dom'
import { createSearchParams, useNavigate } from 'react-router'
import { navigateToInterfaceFiatOnRamp } from 'src/app/features/for/utils'
import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state'
......@@ -123,7 +123,7 @@ function useNavigateToAccountActivityList(): () => void {
const navigateFix = useNavigate()
return useCallback(
(): void =>
(): void | Promise<void> =>
navigateFix({
pathname: AppRoutes.Home,
search: createSearchParams({
......@@ -139,7 +139,7 @@ function useNavigateToAccountTokenList(): () => void {
const navigateFix = useNavigate()
return useCallback(
(): void =>
(): void | Promise<void> =>
navigateFix({
pathname: AppRoutes.Home,
search: createSearchParams({
......@@ -151,7 +151,7 @@ function useNavigateToAccountTokenList(): () => void {
}
function useNavigateToReceive(): () => void {
return useCallback((): void => navigate(AppRoutes.Receive), [])
return useCallback((): void => navigate(`/${AppRoutes.Receive}`), [])
}
function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void {
......@@ -160,7 +160,7 @@ function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void {
const state: SidebarLocationState = args ? { initialTransactionState: initialState } : undefined
navigate(AppRoutes.Send, { state })
navigate(`/${AppRoutes.Send}`, { state })
}, [])
}
......@@ -172,7 +172,7 @@ function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void {
const state: SidebarLocationState = initialState ? { initialTransactionState: initialState } : undefined
navigate(AppRoutes.Swap, { state })
navigate(`/${AppRoutes.Swap}`, { state })
},
[defaultChainId],
)
......
import { useMutation } from '@tanstack/react-query'
import { useEffect, useMemo, useRef } from 'react'
import { useSelector } from 'react-redux'
import { NavigationType, Outlet, ScrollRestoration, useLocation } from 'react-router-dom'
import { NavigationType, Outlet, ScrollRestoration, useLocation } from 'react-router'
import { SmartWalletNudgeModals } from 'src/app/components/modals/SmartWalletNudgeModals'
import { DappRequestQueue } from 'src/app/features/dappRequests/DappRequestQueue'
import { ForceUpgradeModal } from 'src/app/features/forceUpgrade/ForceUpgradeModal'
......
// Shared future flags configuration for createHashRouter
export const ROUTER_FUTURE_FLAGS = {
v7_relativeSplatPath: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
v7_partialHydration: true,
}
// Shared future flags configuration for RouterProvider
export const ROUTER_PROVIDER_FUTURE_FLAGS = {
v7_startTransition: true,
}
import { useEffect, useState } from 'react'
import { Location, NavigationType, Router, createHashRouter } from 'react-router-dom'
import { Location, NavigationType, Router, createHashRouter } from 'react-router'
interface RouterState {
historyAction: NavigationType
......@@ -52,7 +52,7 @@ export function useRouterState(): RouterState | null {
return val
}
// as far as i can tell, react-router-dom doesn't give us this type so have to work around
// as far as i can tell, react-router doesn't give us this type so have to work around
type Router = ReturnType<typeof createHashRouter>
let router: Router | null = null
......
import { To, useLocation } from 'react-router-dom'
import { To, useLocation } from 'react-router'
import { TopLevelRoutes, UnitagClaimRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state'
import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels'
......
......@@ -39,6 +39,7 @@ import {
import { logContentScriptError } from 'src/contentScript/utils'
import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils'
import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { Platform } from 'uniswap/src/features/platforms/types/Platform'
import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { getValidAddress } from 'uniswap/src/utils/addresses'
import { logger } from 'utilities/src/logger/logger'
......@@ -88,7 +89,7 @@ const setChainIdAndMaybeEmit = (newChainId: string): void => {
const setConnectedAddressesAndMaybeEmit = (newConnectedAddresses: Address[]): void => {
// Only emit if the addresses have changed, and it's not the first time
const normalizedNewAddresses: Address[] = newConnectedAddresses
.map((address) => getValidAddress({ address }))
.map((address) => getValidAddress({ address, platform: Platform.EVM }))
.filter((normalizedAddress): normalizedAddress is Address => normalizedAddress !== null)
if (!connectedAddresses || !arraysAreEqual(connectedAddresses, normalizedNewAddresses)) {
......
......@@ -15,6 +15,7 @@ const POLL_ENV = process.env.WEBPACK_POLLING_INTERVAL
process.env.NODE_ENV = NODE_ENV
const isDevelopment = NODE_ENV === 'development'
const isProduction = NODE_ENV === 'production'
const appDirectory = path.resolve(__dirname)
const manifest = require('./src/manifest.json')
......@@ -283,6 +284,12 @@ module.exports = (env) => {
src: path.resolve(__dirname, 'src'), // absolute imports in apps/web
'react-native-gesture-handler$': require.resolve('react-native-gesture-handler'),
'expo-blur': require.resolve('./__mocks__/expo-blur.js'),
'react-router': path.resolve(
__dirname,
isProduction
? '../../node_modules/react-router/dist/production/index.mjs'
: '../../node_modules/react-router/dist/development/index.mjs',
),
},
// Add support for web-based extensions so we can share code between mobile/extension
extensions: [
......
......@@ -4,7 +4,8 @@ gem 'fastlane', '2.214.0'
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '1.14.3'
gem 'activesupport', '7.1.2'
gem 'xcodeproj', '1.26.0'
gem 'xcodeproj', '1.27.0'
gem 'concurrent-ruby', '1.3.4'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
......@@ -81,7 +81,7 @@ GEM
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
concurrent-ruby (1.3.5)
concurrent-ruby (1.3.4)
connection_pool (2.5.0)
declarative (0.0.20)
digest-crc (0.6.5)
......@@ -222,7 +222,7 @@ GEM
multi_json (1.15.0)
multipart-post (2.3.0)
mutex_m (0.3.0)
nanaimo (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
naturally (2.2.1)
netrc (0.11.0)
......@@ -236,7 +236,7 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rexml (3.4.1)
rouge (2.0.7)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
......@@ -266,13 +266,13 @@ GEM
unicode-display_width (1.8.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.23.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
......@@ -284,10 +284,11 @@ PLATFORMS
DEPENDENCIES
activesupport (= 7.1.2)
cocoapods (= 1.14.3)
concurrent-ruby (= 1.3.4)
fastlane (= 2.214.0)
fastlane-plugin-get_version_name
fastlane-plugin-versioning_android
xcodeproj (= 1.26.0)
xcodeproj (= 1.27.0)
BUNDLED WITH
2.4.10
......@@ -6,6 +6,7 @@ plugins {
id 'com.google.gms.google-services'
id 'maven-publish'
id 'kotlin-android'
id 'org.jetbrains.kotlin.plugin.compose'
}
def nodeModulesPath = "../../../../node_modules"
......
......@@ -6,8 +6,8 @@ buildscript {
minSdkVersion = 28
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "26.1.10909125"
kotlinVersion = "1.9.25"
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
appCompat = "1.6.1"
compose = "1.4.3"
......@@ -33,6 +33,7 @@ buildscript {
plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion"
id 'org.jetbrains.kotlin.plugin.compose' version "$kotlinVersion" apply false
}
allprojects {
......
This diff is collapsed.
......@@ -5,6 +5,7 @@
#import "Uniswap-Swift.h"
#import <React/RCTBundleURLProvider.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
#import <ReactNativePerformance/ReactNativePerformance.h>
#import <RCTAppSetupUtils.h>
#import <RNBootSplash.h>
......@@ -56,6 +57,7 @@ static NSString *const hasLaunchedOnceKey = @"HasLaunchedOnce";
}
self.moduleName = @"Uniswap";
self.dependencyProvider = [RCTAppDependencyProvider new];
self.initialProps = @{};
[self.window makeKeyAndVisible];
......
......@@ -13,7 +13,6 @@
"check:deps:usage": "./scripts/checkDepsUsage.sh",
"check:bundlesize": "./scripts/checkBundleSize.sh",
"clean": "react-native-clean-project",
"debug": "react-devtools",
"debug:reactotron:install": "./scripts/installDebugger.sh",
"deduplicate": "yarn-deduplicate --strategy=fewer",
"depcheck": "depcheck",
......@@ -31,6 +30,7 @@
"link:assets": "react-native-asset",
"check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 0",
"ios": "rnef run:ios --scheme Uniswap --configuration Debug && yarn start",
"ios:interactive": "ts-node scripts/ios-build-interactive/main.ts",
"ios:smol": "rnef run:ios --device=\"iPhone SE (3rd generation)\"",
"ios:dev:release": "rnef run:ios --configuration Dev",
"ios:beta": "rnef run:ios --configuration Beta",
......@@ -72,11 +72,11 @@
"@react-native-firebase/auth": "21.0.0",
"@react-native-firebase/firestore": "21.0.0",
"@react-native-masked-view/masked-view": "0.3.2",
"@react-native/metro-config": "0.76.9",
"@react-navigation/core": "6.2.2",
"@react-navigation/native": "6.0.11",
"@react-navigation/native-stack": "6.7.0",
"@react-navigation/stack": "6.2.2",
"@react-native/metro-config": "0.77.2",
"@react-navigation/core": "7.9.2",
"@react-navigation/native": "7.1.9",
"@react-navigation/native-stack": "7.3.13",
"@react-navigation/stack": "7.3.2",
"@reduxjs/toolkit": "1.9.3",
"@reown/walletkit": "1.2.3",
"@rnef/cli": "0.7.18",
......@@ -87,7 +87,7 @@
"@shopify/flash-list": "1.7.3",
"@shopify/react-native-performance": "4.1.2",
"@shopify/react-native-performance-navigation": "3.0.0",
"@shopify/react-native-skia": "1.7.2",
"@shopify/react-native-skia": "1.12.4",
"@sparkfabrik/react-native-idfa-aaid": "1.2.0",
"@tanstack/react-query": "5.77.2",
"@testing-library/react-hooks": "8.0.1",
......@@ -126,14 +126,14 @@
"react": "18.3.1",
"react-freeze": "1.0.3",
"react-i18next": "14.1.0",
"react-native": "0.76.9",
"react-native": "0.77.2",
"react-native-appsflyer": "6.13.1",
"react-native-bootsplash": "6.3.1",
"react-native-context-menu-view": "1.15.0",
"react-native-device-info": "10.11.0",
"react-native-dotenv": "3.2.0",
"react-native-fast-image": "8.6.3",
"react-native-gesture-handler": "2.21.2",
"react-native-gesture-handler": "2.22.1",
"react-native-get-random-values": "1.11.0",
"react-native-image-colors": "1.5.2",
"react-native-image-picker": "7.1.0",
......@@ -147,15 +147,15 @@
"react-native-permissions": "4.1.5",
"react-native-reanimated": "3.16.7",
"react-native-restart": "0.0.27",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "4.1.0",
"react-native-safe-area-context": "5.1.0",
"react-native-screens": "4.11.0",
"react-native-sortables": "1.5.1",
"react-native-svg": "15.10.1",
"react-native-svg": "15.11.2",
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "1.3.0",
"react-native-video": "6.13.0",
"react-native-wagmi-charts": "2.5.2",
"react-native-webview": "13.12.5",
"react-native-webview": "13.13.5",
"react-native-widgetkit": "1.0.9",
"react-redux": "8.0.5",
"redux": "4.2.1",
......@@ -186,7 +186,9 @@
"@storybook/react-native": "8.5.2",
"@tamagui/babel-plugin": "1.125.17",
"@testing-library/react-native": "13.0.0",
"@types/inquirer": "9.0.8",
"@types/jest": "29.5.14",
"@types/node": "22.13.1",
"@types/react": "18.3.18",
"@types/redux-mock-store": "1.0.6",
"@uniswap/eslint-config": "workspace:^",
......@@ -198,6 +200,7 @@
"core-js": "2.6.12",
"eslint": "8.44.0",
"expo-modules-core": "2.2.3",
"inquirer": "12.6.0",
"jest": "29.7.0",
"jest-expo": "52.0.3",
"jest-extended": "4.0.2",
......@@ -205,7 +208,6 @@
"madge": "6.1.0",
"mockdate": "3.0.5",
"postinstall-postinstall": "2.1.0",
"react-devtools": "4.28.0",
"react-dom": "18.3.1",
"react-native-asset": "2.1.1",
"react-native-clean-project": "4.0.1",
......@@ -222,10 +224,12 @@
"expo": {
"install": {
"exclude": [
"react-native@~0.74.0",
"react-native-reanimated@~3.10.0",
"react-native-gesture-handler@~2.16.1",
"react-native-screens@~3.31.1"
"react-native@~0.76.9",
"react-native-reanimated@~3.16.7",
"react-native-gesture-handler@~2.20.0",
"react-native-screens@~4.4.0",
"react-native-safe-area-context@~4.12.0",
"react-native-webview@~13.12.5"
]
},
"autolinking": {
......
# iOS Build Interactive Tool
An interactive CLI tool for building iOS apps with various configurations.
## Structure
- `main.ts` - Main script containing the interactive build logic
- `utils.ts` - Shared utilities, constants, types, and helper functions
## Usage
From the `apps/mobile` directory:
```bash
yarn ios:interactive
```
## Features
- Choose between simulator and device builds
- Select Debug or Release configurations
- Pick from multiple app schemes (Uniswap, Dev, Beta, Production)
- Auto-detect available simulators and devices
- Metro bundler management
- Build cleaning and cache reset options
## Requirements
- Xcode
- Node.js
- Yarn
- CocoaPods
- iOS Simulator (for simulator builds)
- Connected iOS device (for device builds)
This diff is collapsed.
/* eslint-disable no-console */
import { exec, spawn } from 'child_process'
import { promisify } from 'util'
const execAsync = promisify(exec)
// Constants
export const CONSTANTS = {
PORTS: {
METRO: 8081,
},
PATHS: {
MOBILE_DIR: 'apps/mobile',
ENV_FILE: '.env.defaults.local',
BUILD_DIR: 'ios/build',
},
COMMANDS: {
XCODE_VERSION: 'xcodebuild -version',
NODE_VERSION: 'node --version',
YARN_VERSION: 'yarn --version',
POD_VERSION: 'pod --version',
LIST_SIMULATORS: 'xcrun simctl list devices',
LIST_SIMULATORS_JSON: 'xcrun simctl list devices --json',
LIST_DEVICES_NEW: 'xcrun devicectl list devices',
LIST_DEVICES_OLD: 'xcrun instruments -s devices',
CHECK_PORT: 'lsof -i :',
START_METRO: ['yarn', 'start'],
CLEAN_BUILD: 'rm -rf',
POD_INSTALL: 'yarn pod',
},
TIMEOUTS: {
METRO_START: 3000,
METRO_RESET: 2000,
},
MESSAGES: {
EMOJIS: {
CHECK: '🔍',
SUCCESS: '',
ERROR: '',
WARNING: '⚠️',
ROCKET: '🚀',
CLEAN: '🧹',
TRASH: '🗑️',
BUILD: '🔨',
BULB: '💡',
PHONE: '📱',
DEVICE: '📲',
PARTY: '🎉',
WAVE: '👋',
},
ERRORS: {
WRONG_DIR: 'Please run this script from the apps/mobile directory',
ENV_MISSING: 'Environment file missing!',
ENV_DOWNLOAD:
'yarn env:local:download (from apps/mobile) OR yarn mobile env:local:download (from workspace root)',
BUILD_FAILED: 'Build failed with exit code',
PREFLIGHT_FAILED: 'Pre-flight check failed:',
},
},
} as const
// Types
export type BuildType = 'simulator' | 'device'
export type Configuration = 'Debug' | 'Release'
export type Scheme = 'Uniswap'
export interface BuildConfig {
buildType: BuildType
configuration: Configuration
scheme: Scheme
simulator?: string
device?: string
cleanBuild: boolean
resetMetroCache: boolean
}
export interface SimulatorDevice {
name: string
udid: string
state: string
isAvailable: boolean
}
export interface PhysicalDevice {
name: string
udid: string
platform: string
}
export interface PreflightCheck {
name: string
command: string
}
// Utility functions
export const log = {
info: (message: string): void => console.log(message),
success: (message: string): void => console.log(`${CONSTANTS.MESSAGES.EMOJIS.SUCCESS} ${message}`),
error: (message: string): void => console.log(`${CONSTANTS.MESSAGES.EMOJIS.ERROR} ${message}`),
warning: (message: string): void => console.log(`${CONSTANTS.MESSAGES.EMOJIS.WARNING} ${message}`),
}
export const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))
export const runCommand = async (command: string): Promise<{ stdout: string; stderr: string }> => {
return execAsync(command)
}
export const spawnProcess = (command: string, args: string[]): Promise<void> => {
return new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const process = spawn(command, args, { stdio: 'inherit' } as any)
process.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Process failed with exit code ${code}`))
}
})
})
}
export const parseDeviceFromLine = (line: string): PhysicalDevice | null => {
const match = line.match(/^(.+?)\s+\(([0-9.]+)\)\s+\[([A-F0-9-]+)\]/)
if (!match || line.includes('Simulator')) {
return null
}
const [, name, version, udid] = match
if (name && version && udid) {
return {
name: name.trim(),
udid,
platform: `iOS ${version}`,
}
}
return null
}
export const printBuildInfo = (config: BuildConfig, targetType: string): void => {
log.info(`${CONSTANTS.MESSAGES.EMOJIS.BUILD} Building for ${targetType}...`)
log.info(`Configuration: ${config.configuration}`)
log.info(`Scheme: ${config.scheme}`)
const targetName = config.buildType === 'simulator' ? config.simulator : config.device
if (targetName) {
log.info(`Target ${targetType}: ${targetName}`)
}
}
export const printTroubleshootingTips = (isDevice: boolean): void => {
log.info(`\n${CONSTANTS.MESSAGES.EMOJIS.BULB} Troubleshooting suggestions:`)
log.info('1. Try cleaning build folder and resetting Metro cache')
log.info(`2. Ensure all pods are installed: ${CONSTANTS.COMMANDS.POD_INSTALL}`)
log.info('3. Check Xcode for any signing or configuration issues')
if (isDevice) {
log.info('4. Ensure your device is connected and trusted')
log.info('5. Check your signing certificates in Xcode')
log.info('6. Verify your provisioning profiles are valid')
log.info('7. Make sure your device is registered in your Apple Developer account')
} else {
log.info('4. Verify the selected simulator is available')
}
}
export const printHelp = (): void => {
log.info(`${CONSTANTS.MESSAGES.EMOJIS.PHONE} iOS Build Interactive Tool`)
log.info('')
log.info('Interactive CLI tool for building iOS apps with various configurations.')
log.info('')
log.info('Features:')
log.info('• Choose between simulator and device builds')
log.info('• Select Debug or Release configurations')
log.info('• Pick from multiple app schemes')
log.info('• Auto-detect available simulators and devices')
log.info('• Metro bundler management')
log.info('• Build cleaning and cache reset options')
log.info('')
log.info('Usage: yarn ios:interactive')
}
// Prompt configurations
export const PROMPT_CONFIGS = {
buildType: {
type: 'list' as const,
name: 'buildType' as const,
message: 'What type of build do you want?',
choices: [
{ name: `${CONSTANTS.MESSAGES.EMOJIS.PHONE} iOS Simulator`, value: 'simulator' },
{ name: `${CONSTANTS.MESSAGES.EMOJIS.DEVICE} Physical Device`, value: 'device' },
],
},
configuration: {
type: 'list' as const,
name: 'configuration' as const,
message: 'Select build configuration:',
choices: [
{ name: 'Debug (faster build, debugging enabled)', value: 'Debug' },
{ name: 'Release (optimized, production-ready)', value: 'Release' },
],
default: 'Debug',
},
utilities: {
type: 'checkbox' as const,
name: 'utilities' as const,
message: 'Select additional options:',
choices: [
{ name: 'Clean build folder before building', value: 'clean' },
{ name: 'Reset Metro cache', value: 'resetCache' },
],
},
}
......@@ -2,7 +2,7 @@
set -e
REQUIRED_XCODE_VERSION="16.3"
REQUIRED_XCODE_VERSION="16.4"
UPDATE_REPOS=false
while [[ $# -gt 0 ]]; do
......
......@@ -200,8 +200,8 @@ function AppOuter(): JSX.Element | null {
loading_time: report.timeToBootJsMillis,
})
jsBundleLoadedRef.current = true
}
if (report.interactive) {
// Note that we are not checking report.interactive here because it's not consistently reported.
// Additionally, we are not tracking interactive the same way @shopify/react-native-performance does.
await DdRum.addTiming(DDRumTiming.ScreenInteractive)
}
}
......
import { PropsWithChildren, useCallback } from 'react'
import { Share } from 'react-native'
import { useDispatch } from 'react-redux'
import { exploreNavigationRef } from 'src/app/navigation/navigationRef'
import { exploreNavigationRef, navigationRef } from 'src/app/navigation/navigationRef'
import { useAppStackNavigation } from 'src/app/navigation/types'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { closeAllModals, closeModal, openModal } from 'src/features/modals/modalSlice'
......@@ -195,13 +195,22 @@ function useNavigateToTokenDetails(): (currencyId: string) => void {
return useCallback(
(currencyId: string): void => {
const isExploreNavigationActuallyFocused = Boolean(
navigationRef.getCurrentRoute()?.name === ModalName.Explore &&
exploreNavigationRef.current &&
exploreNavigationRef.isFocused(),
)
closeKeyboardBeforeCallback(() => {
onClose()
dispatch(closeAllModals())
if (exploreNavigationRef.current && exploreNavigationRef.isFocused()) {
if (isExploreNavigationActuallyFocused) {
exploreNavigationRef.navigate(MobileScreens.TokenDetails, { currencyId })
} else {
appNavigation.navigate(MobileScreens.TokenDetails, { currencyId })
onClose()
appNavigation.reset({
index: 1,
routes: [{ name: MobileScreens.Home }, { name: MobileScreens.TokenDetails, params: { currencyId } }],
})
}
})
},
......
......@@ -261,7 +261,13 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
variant="subheading1"
/>
<Flex row px="$spacing12">
<Button size="medium" testID={TestID.WalletSettings} emphasis="secondary" onPress={onManageWallet}>
<Button
lineHeightDisabled
size="medium"
testID={TestID.WalletSettings}
emphasis="secondary"
onPress={onManageWallet}
>
{t('account.wallet.button.manage')}
</Button>
</Flex>
......@@ -277,7 +283,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
<TouchableArea mt="$spacing16" testID={TestID.AccountSwitcherAddWallet} onPress={onPressAddWallet}>
<Flex row alignItems="center" gap="$spacing8" ml="$spacing24">
<PlusCircle />
<Text color="$neutral1" variant="buttonLabel2">
<Text numberOfLines={1} width="100%" color="$neutral1" variant="buttonLabel2">
{t('account.wallet.button.add')}
</Text>
</Flex>
......
......@@ -89,7 +89,64 @@ exports[`AccountSwitcher renders correctly 1`] = `
"width": 56,
}
}
/>
>
<View
style={
{
"height": 56,
"width": 56,
}
}
>
<skCircle
color="#00C3A01F"
cx={28}
cy={28}
r={28}
/>
<skGroup
transform={
[
{
"translateX": 9.333333333333332,
},
{
"translateY": 9.333333333333332,
},
]
}
>
<skGroup
transform={
[
{
"scale": 0.7777777777777778,
},
]
}
>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M20.9454 0.0175773C22.4936 -0.167833 23.763 1.13917 23.763 2.72944V21.3625C23.763 22.9527 22.4993 24.2419 20.9405 24.2419L2.67549 24.2419C1.11665 24.2419 -0.16452 22.9469 0.0172307 21.3675C1.30306 10.1936 9.99222 1.32931 20.9454 0.0175773Z"
start={0}
strokeWidth={1}
/>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M27.0546 47.9824C25.5064 48.1678 24.237 46.8608 24.237 45.2706V26.6375C24.237 25.0473 25.5007 23.7581 27.0595 23.7581L45.3245 23.7581C46.8833 23.7581 48.1645 25.0531 47.9828 26.6325C46.6969 37.8064 38.0078 46.6707 27.0546 47.9824Z"
start={0}
strokeWidth={1}
/>
</skGroup>
</skGroup>
</View>
</View>
</View>
<View
style={
......@@ -357,6 +414,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
testID="wallet-settings"
>
<Text
line-height-disabled="true"
maxFontSizeMultiplier={1.2}
numberOfLines={1}
onBlur={[Function]}
......@@ -372,7 +430,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
"fontFamily": "Basel Grotesk",
"fontSize": 17,
"fontWeight": "500",
"lineHeight": 21.849999999999998,
"textAlign": "center",
}
}
suppressHighlighting={true}
......@@ -422,16 +480,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
scrollEventThrottle={0.0001}
stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]}
waitFor={
[
{
"current": null,
},
{
"current": null,
},
]
}
>
<View />
</RCTScrollView>
......@@ -602,6 +650,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
<Text
allowFontScaling={true}
maxFontSizeMultiplier={1.2}
numberOfLines={1}
style={
{
"color": {
......@@ -614,6 +663,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
"fontSize": 17,
"fontWeight": "500",
"lineHeight": 19.549999999999997,
"width": "100%",
}
}
suppressHighlighting={true}
......
import { NavigationContainer } from '@react-navigation/native'
import { DefaultTheme, NavigationContainer, NavigationIndependentTree } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import React from 'react'
import { navNativeStackOptions } from 'src/app/navigation/navStackOptions'
......@@ -20,40 +20,42 @@ export function ExploreStackNavigator(): JSX.Element {
const colors = useSporeColors()
return (
<NavigationContainer
ref={exploreNavigationRef}
independent
theme={{
dark: false,
colors: {
primary: 'transparent',
background: 'transparent',
card: 'transparent',
text: 'transparent',
border: 'transparent',
notification: 'transparent',
},
}}
onStateChange={stopTracking}
onReady={() => startTracking(exploreNavigationRef)}
>
<HorizontalEdgeGestureTarget />
<ExploreStack.Navigator
initialRouteName={MobileScreens.Explore}
screenOptions={navNativeStackOptions.independentBsm}
<NavigationIndependentTree>
<NavigationContainer
ref={exploreNavigationRef}
theme={{
...DefaultTheme,
dark: false,
colors: {
primary: 'transparent',
background: 'transparent',
card: 'transparent',
text: 'transparent',
border: 'transparent',
notification: 'transparent',
},
}}
onStateChange={stopTracking}
onReady={() => startTracking(exploreNavigationRef)}
>
<ExploreStack.Screen component={ExploreScreen} name={MobileScreens.Explore} />
<ExploreStack.Group screenOptions={{ contentStyle: { backgroundColor: colors.surface1.val } }}>
<ExploreStack.Screen name={MobileScreens.ExternalProfile}>
{(props): JSX.Element => <ExternalProfileScreen {...props} renderedInModal />}
</ExploreStack.Screen>
<ExploreStack.Screen name={MobileScreens.NFTCollection}>
{(props): JSX.Element => <NFTCollectionScreen {...props} renderedInModal />}
</ExploreStack.Screen>
<ExploreStack.Screen component={NFTItemScreen} name={MobileScreens.NFTItem} />
<ExploreStack.Screen component={TokenDetailsScreen} name={MobileScreens.TokenDetails} />
</ExploreStack.Group>
</ExploreStack.Navigator>
</NavigationContainer>
<HorizontalEdgeGestureTarget />
<ExploreStack.Navigator
initialRouteName={MobileScreens.Explore}
screenOptions={navNativeStackOptions.independentBsm}
>
<ExploreStack.Screen component={ExploreScreen} name={MobileScreens.Explore} />
<ExploreStack.Group screenOptions={{ contentStyle: { backgroundColor: colors.surface1.val } }}>
<ExploreStack.Screen name={MobileScreens.ExternalProfile}>
{(props): JSX.Element => <ExternalProfileScreen {...props} renderedInModal />}
</ExploreStack.Screen>
<ExploreStack.Screen name={MobileScreens.NFTCollection}>
{(props): JSX.Element => <NFTCollectionScreen {...props} renderedInModal />}
</ExploreStack.Screen>
<ExploreStack.Screen component={NFTItemScreen} name={MobileScreens.NFTItem} />
<ExploreStack.Screen component={TokenDetailsScreen} name={MobileScreens.TokenDetails} />
</ExploreStack.Group>
</ExploreStack.Navigator>
</NavigationContainer>
</NavigationIndependentTree>
)
}
import { NavigationContainer } from '@react-navigation/native'
import { NavigationContainer, NavigationIndependentTree } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { TransitionPresets, createStackNavigator } from '@react-navigation/stack'
import React, { useEffect } from 'react'
......@@ -196,27 +196,28 @@ function WrappedHomeScreen(props: AppStackScreenProp<MobileScreens.Home>): JSX.E
export function FiatOnRampStackNavigator(): JSX.Element {
return (
<NavigationContainer
ref={fiatOnRampNavigationRef}
independent
onReady={() => startTracking(fiatOnRampNavigationRef)}
onStateChange={stopTracking}
>
<HorizontalEdgeGestureTarget />
<FiatOnRampProvider>
<FiatOnRampStack.Navigator
initialRouteName={FiatOnRampScreens.AmountInput}
screenOptions={navNativeStackOptions.independentBsm}
>
<FiatOnRampStack.Screen component={FiatOnRampScreen} name={FiatOnRampScreens.AmountInput} />
<FiatOnRampStack.Screen
component={FiatOnRampServiceProvidersScreen}
name={FiatOnRampScreens.ServiceProviders}
/>
<FiatOnRampStack.Screen component={FiatOnRampConnectingScreen} name={FiatOnRampScreens.Connecting} />
</FiatOnRampStack.Navigator>
</FiatOnRampProvider>
</NavigationContainer>
<NavigationIndependentTree>
<NavigationContainer
ref={fiatOnRampNavigationRef}
onReady={() => startTracking(fiatOnRampNavigationRef)}
onStateChange={stopTracking}
>
<HorizontalEdgeGestureTarget />
<FiatOnRampProvider>
<FiatOnRampStack.Navigator
initialRouteName={FiatOnRampScreens.AmountInput}
screenOptions={navNativeStackOptions.independentBsm}
>
<FiatOnRampStack.Screen component={FiatOnRampScreen} name={FiatOnRampScreens.AmountInput} />
<FiatOnRampStack.Screen
component={FiatOnRampServiceProvidersScreen}
name={FiatOnRampScreens.ServiceProviders}
/>
<FiatOnRampStack.Screen component={FiatOnRampConnectingScreen} name={FiatOnRampScreens.Connecting} />
</FiatOnRampStack.Navigator>
</FiatOnRampProvider>
</NavigationContainer>
</NavigationIndependentTree>
)
}
......@@ -316,7 +317,7 @@ function UnitagStackNavigator(): JSX.Element {
screenOptions={{
headerMode: 'float',
headerTitle: '',
headerBackTitleVisible: false,
headerBackButtonDisplayMode: 'minimal',
headerBackImage: renderHeaderBackImage,
headerStatusBarHeight: insets.top + spacing.spacing8,
headerTransparent: true,
......@@ -390,6 +391,7 @@ export function AppStackNavigator(): JSX.Element {
<AppStack.Screen component={NFTCollectionScreen} name={MobileScreens.NFTCollection} />
<AppStack.Screen component={WebViewScreen} name={MobileScreens.WebView} />
<AppStack.Screen component={SettingsStackGroup} name={MobileScreens.SettingsStack} />
<AppStack.Screen component={ViewPrivateKeysScreen} name={MobileScreens.ViewPrivateKeys} />
<AppStack.Group screenOptions={navNativeStackOptions.presentationModal}>
<AppStack.Screen component={EducationScreen} name={MobileScreens.Education} />
</AppStack.Group>
......
import { createNavigationContainerRef } from '@react-navigation/native'
import { ExploreStackParamList, FiatOnRampStackParamList } from 'src/app/navigation/types'
import { ExploreStackParamList, FiatOnRampStackParamList, RootParamList } from 'src/app/navigation/types'
// this was moved to its own file to avoid circular dependencies
export const navigationRef = createNavigationContainerRef()
export const navigationRef = createNavigationContainerRef<RootParamList>()
export const exploreNavigationRef = createNavigationContainerRef<ExploreStackParamList>()
export const fiatOnRampNavigationRef = createNavigationContainerRef<FiatOnRampStackParamList>()
export const navRefs = [exploreNavigationRef, fiatOnRampNavigationRef, navigationRef]
/* eslint-disable @typescript-eslint/no-explicit-any */
import { NavigationAction, NavigationState } from '@react-navigation/core'
import { navigationRef } from 'src/app/navigation/navigationRef'
import { RootParamList } from 'src/app/navigation/types'
......@@ -23,9 +24,9 @@ export function navigate<RouteName extends keyof RootParamList>(...args: RootNav
return
}
// Type assignment to `never` is a workaround until we figure out how to
// Type assignment to `any` is a workaround until we figure out how to
// type `createNavigationContainerRef` in a way that's compatible
navigationRef.navigate(routeName as never, params as never)
navigationRef.navigate(routeName as any, params as never)
}
export function dispatchNavigationAction(
......
......@@ -13,6 +13,7 @@ import {
import { Flex, Text } from 'ui/src'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { Platform } from 'uniswap/src/features/platforms/types/Platform'
import { EthTransaction } from 'uniswap/src/types/walletConnect'
import { getValidAddress } from 'uniswap/src/utils/addresses'
import { logger } from 'utilities/src/logger/logger'
......@@ -92,7 +93,11 @@ const getParsedObjectDisplay = ({
const childValue = obj[objKey]
// Special case for address strings
if (typeof childValue === 'string' && getValidAddress({ address: childValue, withChecksum: true })) {
// TODO(WALL-7065): Handle SVM address validation as well
if (
typeof childValue === 'string' &&
getValidAddress({ address: childValue, platform: Platform.EVM, withEVMChecksum: true })
) {
return (
<KeyValueRow key={objKey} objKey={objKey}>
<Flex>
......
......@@ -8,13 +8,13 @@ import { Flex, SpinningLoader, Text, useIsDarkMode } from 'ui/src'
import { iconSizes, spacing } from 'ui/src/theme'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import { NetworkFee } from 'uniswap/src/components/gas/NetworkFee'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { getChainLabel } from 'uniswap/src/features/chains/utils'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { useOnChainCurrencyBalance } from 'uniswap/src/features/portfolio/api'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
import { NumberType } from 'utilities/src/format/types'
......@@ -99,7 +99,7 @@ function UwULinkErc20SendModalContent({
const { convertFiatAmountFormatted } = useLocalizationContext()
const { chainId, isStablecoin } = request
const nativeCurrency = NativeCurrency.onChain(chainId)
const nativeCurrency = nativeOnChain(chainId)
if (loading || !currencyInfo) {
return (
......@@ -153,7 +153,7 @@ function UwULinkErc20SendModalContent({
{!hasSufficientGasFunds && (
<Text color="$statusWarning" pt="$spacing8" textAlign="center" variant="body3">
{t('walletConnect.request.error.insufficientFunds', {
currencySymbol: nativeCurrency.symbol,
currencySymbol: nativeCurrency.symbol ?? '',
})}
</Text>
)}
......
......@@ -12,9 +12,9 @@ import {
import { Flex, Text } from 'ui/src'
import { AlertTriangleFilled } from 'ui/src/components/icons'
import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
import { BlockedAddressWarning } from 'uniswap/src/features/transactions/modals/BlockedAddressWarning'
import { isPrimaryTypePermit } from 'uniswap/src/types/walletConnect'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
......@@ -69,7 +69,7 @@ export function WalletConnectRequestModalContent({
}: WalletConnectRequestModalContentProps): JSX.Element {
const chainId = request.chainId
const permitInfo = getPermitInfo(request)
const nativeCurrency = NativeCurrency.onChain(chainId)
const nativeCurrency = nativeOnChain(chainId)
const { t } = useTranslation()
const { animatedFooterHeight } = useBottomSheetInternal()
......@@ -115,7 +115,7 @@ export function WalletConnectRequestModalContent({
<Flex p="$spacing16">
<Text color="$statusWarning" variant="body2">
{t('walletConnect.request.error.insufficientFunds', {
currencySymbol: nativeCurrency.symbol,
currencySymbol: nativeCurrency.symbol ?? '',
})}
</Text>
</Flex>
......
import { useMemo } from 'react'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils'
import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api'
import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount'
export function useHasSufficientFunds({
......@@ -19,7 +19,7 @@ export function useHasSufficientFunds({
value?: string
}): boolean {
const { defaultChainId } = useEnabledChains()
const nativeCurrency = NativeCurrency.onChain(chainId || defaultChainId)
const nativeCurrency = nativeOnChain(chainId || defaultChainId)
const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(chainId ?? defaultChainId, account)
const hasSufficientFunds = useMemo(() => {
......
......@@ -11,6 +11,7 @@ import {
UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM,
UNISWAP_WALLETCONNECT_URL,
} from 'src/features/deepLinking/constants'
import { Platform } from 'uniswap/src/features/platforms/types/Platform'
import { getValidAddress } from 'uniswap/src/utils/addresses'
import { logger } from 'utilities/src/logger/logger'
import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types'
......@@ -45,7 +46,12 @@ export async function getSupportedURI(
return undefined
}
const maybeAddress = getValidAddress({ address: uri, withChecksum: true, log: false })
const maybeAddress = getValidAddress({
address: uri,
platform: Platform.EVM,
withEVMChecksum: true,
log: false,
})
if (maybeAddress) {
return { type: URIType.Address, value: maybeAddress }
}
......@@ -123,7 +129,7 @@ function getMetamaskAddress(uri: string): Nullable<string> {
return null
}
return getValidAddress({ address: uriParts[1], withChecksum: true, log: false })
return getValidAddress({ address: uriParts[1], platform: Platform.EVM, withEVMChecksum: true, log: false })
}
// format is uniswap://scantastic?<params>
......
......@@ -47,7 +47,7 @@ describe('PrivateKeySpeedBumpModal', () => {
const continueButton = screen.getByTestId(TestID.Continue)
fireEvent.press(continueButton)
expect(mockPreventCloseRef.current).toBe(true)
expect(mockOnClose).toHaveBeenCalled()
expect(mockNavigation.navigate).toHaveBeenCalledWith(MobileScreens.ViewPrivateKeys)
})
})
......@@ -20,10 +20,10 @@ export function PrivateKeySpeedBumpModal({
navigation,
}: AppStackScreenProp<typeof ModalName.PrivateKeySpeedBumpModal>): JSX.Element | null {
const colors = useSporeColors()
const { onClose, preventCloseRef } = useReactNavigationModal()
const { onClose } = useReactNavigationModal()
const onContinue = (): void => {
preventCloseRef.current = true
onClose()
navigation.navigate(MobileScreens.ViewPrivateKeys)
}
......
......@@ -506,6 +506,7 @@ exports[`PrivateKeySpeedBumpModal renders correctly 1`] = `
testID="continue"
>
<Text
line-height-disabled="false"
maxFontSizeMultiplier={1.2}
numberOfLines={1}
onBlur={[Function]}
......@@ -522,6 +523,7 @@ exports[`PrivateKeySpeedBumpModal renders correctly 1`] = `
"fontSize": 17,
"fontWeight": "500",
"lineHeight": 21.849999999999998,
"textAlign": "center",
}
}
suppressHighlighting={true}
......
......@@ -188,7 +188,7 @@ export function ManageWalletsModal({ route }: AppStackScreenProp<typeof ModalNam
/>
</Flex>
<Flex row pb="$padding20" pt="$padding12">
<Button variant="critical" emphasis="secondary" onPress={onRemoveWallet}>
<Button lineHeightDisabled variant="critical" emphasis="secondary" onPress={onRemoveWallet}>
{t('settings.setting.wallet.action.remove')}
</Button>
</Flex>
......
......@@ -110,7 +110,11 @@ export const SettingsRow = memo(
if (onToggle) {
return
} else if (screen) {
navigation.navigate(screen, screenProps)
/* eslint-disable @typescript-eslint/no-explicit-any */
// Type assignment to `any` is a workaround until we figure out how to
// properly type screen param. `navigate` function also brings some issues,
// where it accepts other screen's params, and not throws an error on required ones.
navigation.navigate(screen as any, screenProps)
} else if (navigationModal) {
navigate(navigationModal, navigationProps)
} else if (externalLink) {
......
......@@ -140,7 +140,64 @@ exports[`AccountCardItem renders correctly 1`] = `
"width": 32,
}
}
/>
>
<View
style={
{
"height": 32,
"width": 32,
}
}
>
<skCircle
color="#00C3A01F"
cx={16}
cy={16}
r={16}
/>
<skGroup
transform={
[
{
"translateX": 5.333333333333334,
},
{
"translateY": 5.333333333333334,
},
]
}
>
<skGroup
transform={
[
{
"scale": 0.4444444444444444,
},
]
}
>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M20.9454 0.0175773C22.4936 -0.167833 23.763 1.13917 23.763 2.72944V21.3625C23.763 22.9527 22.4993 24.2419 20.9405 24.2419L2.67549 24.2419C1.11665 24.2419 -0.16452 22.9469 0.0172307 21.3675C1.30306 10.1936 9.99222 1.32931 20.9454 0.0175773Z"
start={0}
strokeWidth={1}
/>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M27.0546 47.9824C25.5064 48.1678 24.237 46.8608 24.237 45.2706V26.6375C24.237 25.0473 25.5007 23.7581 27.0595 23.7581L45.3245 23.7581C46.8833 23.7581 48.1645 25.0531 47.9828 26.6325C46.6969 37.8064 38.0078 46.6707 27.0546 47.9824Z"
start={0}
strokeWidth={1}
/>
</skGroup>
</skGroup>
</View>
</View>
</View>
</View>
<View
......
......@@ -125,7 +125,64 @@ exports[`AccountHeader renders correctly 1`] = `
"width": 52,
}
}
/>
>
<View
style={
{
"height": 52,
"width": 52,
}
}
>
<skCircle
color="#00C3A01F"
cx={26}
cy={26}
r={26}
/>
<skGroup
transform={
[
{
"translateX": 8.666666666666668,
},
{
"translateY": 8.666666666666668,
},
]
}
>
<skGroup
transform={
[
{
"scale": 0.7222222222222222,
},
]
}
>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M20.9454 0.0175773C22.4936 -0.167833 23.763 1.13917 23.763 2.72944V21.3625C23.763 22.9527 22.4993 24.2419 20.9405 24.2419L2.67549 24.2419C1.11665 24.2419 -0.16452 22.9469 0.0172307 21.3675C1.30306 10.1936 9.99222 1.32931 20.9454 0.0175773Z"
start={0}
strokeWidth={1}
/>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M27.0546 47.9824C25.5064 48.1678 24.237 46.8608 24.237 45.2706V26.6375C24.237 25.0473 25.5007 23.7581 27.0595 23.7581L45.3245 23.7581C46.8833 23.7581 48.1645 25.0531 47.9828 26.6325C46.6969 37.8064 38.0078 46.6707 27.0546 47.9824Z"
start={0}
strokeWidth={1}
/>
</skGroup>
</skGroup>
</View>
</View>
</View>
</View>
<View
......
......@@ -56,16 +56,6 @@ exports[`AccountList renders without error 1`] = `
scrollEventThrottle={0.0001}
stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]}
waitFor={
[
{
"current": null,
},
{
"current": null,
},
]
}
>
<View>
<View
......@@ -212,7 +202,64 @@ exports[`AccountList renders without error 1`] = `
"width": 32,
}
}
/>
>
<View
style={
{
"height": 32,
"width": 32,
}
}
>
<skCircle
color="#00C3A01F"
cx={16}
cy={16}
r={16}
/>
<skGroup
transform={
[
{
"translateX": 5.333333333333334,
},
{
"translateY": 5.333333333333334,
},
]
}
>
<skGroup
transform={
[
{
"scale": 0.4444444444444444,
},
]
}
>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M20.9454 0.0175773C22.4936 -0.167833 23.763 1.13917 23.763 2.72944V21.3625C23.763 22.9527 22.4993 24.2419 20.9405 24.2419L2.67549 24.2419C1.11665 24.2419 -0.16452 22.9469 0.0172307 21.3675C1.30306 10.1936 9.99222 1.32931 20.9454 0.0175773Z"
start={0}
strokeWidth={1}
/>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M27.0546 47.9824C25.5064 48.1678 24.237 46.8608 24.237 45.2706V26.6375C24.237 25.0473 25.5007 23.7581 27.0595 23.7581L45.3245 23.7581C46.8833 23.7581 48.1645 25.0531 47.9828 26.6325C46.6969 37.8064 38.0078 46.6707 27.0546 47.9824Z"
start={0}
strokeWidth={1}
/>
</skGroup>
</skGroup>
</View>
</View>
</View>
</View>
<View
......
......@@ -164,7 +164,64 @@ exports[`FavoriteWalletCard renders without error 1`] = `
"width": 20,
}
}
/>
>
<View
style={
{
"height": 20,
"width": 20,
}
}
>
<skCircle
color="#00C3A01F"
cx={10}
cy={10}
r={10}
/>
<skGroup
transform={
[
{
"translateX": 3.333333333333333,
},
{
"translateY": 3.333333333333333,
},
]
}
>
<skGroup
transform={
[
{
"scale": 0.2777777777777778,
},
]
}
>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M20.9454 0.0175773C22.4936 -0.167833 23.763 1.13917 23.763 2.72944V21.3625C23.763 22.9527 22.4993 24.2419 20.9405 24.2419L2.67549 24.2419C1.11665 24.2419 -0.16452 22.9469 0.0172307 21.3675C1.30306 10.1936 9.99222 1.32931 20.9454 0.0175773Z"
start={0}
strokeWidth={1}
/>
<skPath
clip-rule="evenodd"
color="#00C3A0"
end={1}
fillType="evenOdd"
path="M27.0546 47.9824C25.5064 48.1678 24.237 46.8608 24.237 45.2706V26.6375C24.237 25.0473 25.5007 23.7581 27.0595 23.7581L45.3245 23.7581C46.8833 23.7581 48.1645 25.0531 47.9828 26.6325C46.6969 37.8064 38.0078 46.6707 27.0546 47.9824Z"
start={0}
strokeWidth={1}
/>
</skGroup>
</skGroup>
</View>
</View>
</View>
<View
style={
......
......@@ -2,9 +2,14 @@ import { act, renderHook } from '@testing-library/react-hooks'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
const mockGoBack = jest.fn()
const mockIsFocused = jest.fn(() => true)
const mockCanGoBack = jest.fn(() => true)
jest.mock('src/app/navigation/types', () => ({
useAppStackNavigation: jest.fn(() => ({
goBack: mockGoBack,
isFocused: mockIsFocused,
canGoBack: mockCanGoBack,
})),
}))
......@@ -36,4 +41,14 @@ describe('useReactNavigationModal', () => {
})
expect(mockGoBack).not.toHaveBeenCalled()
})
it('should not call navigation.goBack when navigation is not focused', () => {
mockIsFocused.mockReturnValue(false)
const { result } = renderHook(() => useReactNavigationModal())
act(() => {
result.current.onClose()
})
expect(mockGoBack).not.toHaveBeenCalled()
expect(result.current.preventCloseRef.current).toBe(false)
})
})
......@@ -19,11 +19,13 @@ export function useReactNavigationModal(): {
const preventCloseRef = useRef(false)
const onClose = useCallback(() => {
if (preventCloseRef.current) {
if (preventCloseRef.current || !navigation.isFocused()) {
return
}
preventCloseRef.current = true
navigation.goBack()
if (navigation.canGoBack()) {
navigation.goBack()
}
}, [navigation])
return {
......
......@@ -35,7 +35,8 @@ import {
setHasPendingSessionError,
} from 'src/features/walletConnect/walletConnectSlice'
import { call, fork, put, select, take } from 'typed-redux-saga'
import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/types'
import { ALL_CHAIN_IDS } from 'uniswap/src/features/chains/chainInfo'
import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { getChainLabel } from 'uniswap/src/features/chains/utils'
import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { isSelfCallWithData } from 'uniswap/src/features/dappRequests/utils'
......
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