ci(release): publish latest release

parent db1fbc5e
...@@ -6,28 +6,7 @@ alwaysApply: false ...@@ -6,28 +6,7 @@ alwaysApply: false
# Mobile Styling Conventions # Mobile Styling Conventions
## Component Styling ## Component Styling
- Use the `styled` function from `ui/src` for consistent styling - Prefer Tamagui inline props over other methods
- 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',
},
})
```
## Theme Usage ## Theme Usage
- Use theme tokens from the UI package instead of hardcoded values - Use theme tokens from the UI package instead of hardcoded values
...@@ -40,9 +19,11 @@ const styles = StyleSheet.create({ ...@@ -40,9 +19,11 @@ const styles = StyleSheet.create({
- Minimize view hierarchy depth - Minimize view hierarchy depth
## Platform Specific Code ## Platform Specific Code
- Use `$platform-web` for platform-specific styling in Tamagui - Use `.<platform>.tsx` extensions for platform-specific components
- Use `.ios.tsx` and `.android.tsx` extensions for platform-specific components - The bundler will grab the appropriate file during the build and always fallback to `.tsx`
- Use the `Platform.select` API for inline platform-specific code - 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 ## Performance
- Memoize complex style calculations - Memoize complex style calculations
......
--- ---
description: Component Structure and Best Practices description:
globs: globs:
alwaysApply: false alwaysApply: true
--- ---
# Component Structure and Best Practices # Component Structure and Best Practices
## Component Organization ## Component Organization
- Place state and hooks at the top of the component - Place state and hooks at the top of the component
- Group related state variables together - Group related state variables together
- Extract complex logic into custom hooks
- Define handlers after state declarations - Define handlers after state declarations
- Place JSX return statement at the end of the component - 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 ## Component Structure Example
```typescript ```typescript
interface ExampleComponentProps {
prop1: string;
prop2: () => void;
}
export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.Element { export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.Element {
// State declarations // State declarations
const [state1, setState1] = useState(false) const [state1, setState1] = useState(false)
const [state2, setState2] = useState<string>('') const [state2, setState2] = useState<string>('')
// Custom hooks // Queries and mutations
const { data, loading } = useCustomHook() const { data, isPending } = useQuery(exampleQueries.getData(prop1))
const mutation = useMutation({
mutationFn: () => exampleService.submit(prop1),
onSuccess: prop2
})
// Derived values // Derived values
const derivedValue = useMemo(() => { const derivedValue = useMemo(() => {
return someCalculation(state1, prop1) return someCalculation(state1, data)
}, [state1, prop1]) }, [state1, data])
// Event handlers // Event handlers
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
setState1(!state1) setState1(!state1)
}, [state1]) mutation.mutate()
}, [state1, mutation])
// Side effects // Side effects
useEffect(() => { useEffect(() => {
...@@ -38,7 +67,7 @@ export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.E ...@@ -38,7 +67,7 @@ export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.E
}, [prop2]) }, [prop2])
// Conditional rendering logic // Conditional rendering logic
if (loading) { if (isPending) {
return <LoadingSpinner /> return <LoadingSpinner />
} }
...@@ -51,21 +80,3 @@ export function ExampleComponent({ prop1, prop2 }: ExampleComponentProps): JSX.E ...@@ -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: [ ...@@ -6,6 +6,7 @@ ignores: [
'i18next', 'i18next',
'moti', 'moti',
'wrangler', 'wrangler',
'react-router',
# Dependencies that depcheck thinks are missing but are actually present or never used # Dependencies that depcheck thinks are missing but are actually present or never used
'@yarnpkg/core', '@yarnpkg/core',
'@yarnpkg/cli', '@yarnpkg/cli',
......
...@@ -69,3 +69,4 @@ CLAUDE.local.md ...@@ -69,3 +69,4 @@ CLAUDE.local.md
# cursor # cursor
.cursor/mcp.json .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 diff --git a/RNFastImage.podspec b/RNFastImage.podspec
index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612c682ae0b 100644 index db0fada63fc06191f8620d336d244edde6c3dba3..93d7291b183b9625ad8d50e812ae247a11bad9d3 100644
--- a/RNFastImage.podspec --- a/RNFastImage.podspec
+++ b/RNFastImage.podspec +++ b/RNFastImage.podspec
@@ -16,6 +16,6 @@ Pod::Spec.new do |s| @@ -16,6 +16,6 @@ Pod::Spec.new do |s|
...@@ -7,6 +7,7 @@ index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612 ...@@ -7,6 +7,7 @@ index db0fada63fc06191f8620d336d244edde6c3dba3..286fa816e47996fdff9f25261644d612
s.dependency 'React-Core' s.dependency 'React-Core'
- s.dependency 'SDWebImage', '~> 5.11.1' - 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 end
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmSighTNwdbMwNVFqAKsFqh2CRmBQ6ZwqVF52RRwHa19gG` - CIDv0: `QmR1FhjPj6guoB8B8Qq5g3H6MrcfPXPeM8GsTBQZprHsxQ`
- CIDv1: `bafybeicbcpddgur5bzzdq2tlgvnf6t3l4fzi74tfuo5b2luhk2bku3f2su` - CIDv1: `bafybeibhtmbqzvasqpaerlcxgwm5gxaomyjsrq3fxs5mg564vmtgcweqau`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). 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. ...@@ -10,14 +10,63 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs. Your Uniswap settings are never remembered across different URLs.
IPFS gateways: IPFS gateways:
- https://bafybeicbcpddgur5bzzdq2tlgvnf6t3l4fzi74tfuo5b2luhk2bku3f2su.ipfs.dweb.link/ - https://bafybeibhtmbqzvasqpaerlcxgwm5gxaomyjsrq3fxs5mg564vmtgcweqau.ipfs.dweb.link/
- [ipfs://QmSighTNwdbMwNVFqAKsFqh2CRmBQ6ZwqVF52RRwHa19gG/](ipfs://QmSighTNwdbMwNVFqAKsFqh2CRmBQ6ZwqVF52RRwHa19gG/) - [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 ### 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 web/5.103.0
\ No newline at end of file \ No newline at end of file
...@@ -31,14 +31,14 @@ ...@@ -31,14 +31,14 @@
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-i18next": "14.1.0", "react-i18next": "14.1.0",
"react-native": "0.76.9", "react-native": "0.77.2",
"react-native-gesture-handler": "2.21.2", "react-native-gesture-handler": "2.22.1",
"react-native-reanimated": "3.16.7", "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-native-web": "0.19.13",
"react-qr-code": "2.0.12", "react-qr-code": "2.0.12",
"react-redux": "8.0.5", "react-redux": "8.0.5",
"react-router-dom": "6.30.1", "react-router": "7.6.3",
"redux": "4.2.1", "redux": "4.2.1",
"redux-logger": "3.0.6", "redux-logger": "3.0.6",
"redux-persist": "6.0.0", "redux-persist": "6.0.0",
......
import { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
import { useRouteError } from 'react-router-dom' import { useRouteError } from 'react-router'
export function ErrorElement({ children }: PropsWithChildren<unknown>): JSX.Element { export function ErrorElement({ children }: PropsWithChildren<unknown>): JSX.Element {
const error = useRouteError() const error = useRouteError()
......
...@@ -3,7 +3,7 @@ import 'src/app/Global.css' ...@@ -3,7 +3,7 @@ import 'src/app/Global.css'
import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, order matters import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, order matters
import { useEffect } from 'react' 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 { PersistGate } from 'redux-persist/integration/react'
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'
...@@ -35,7 +35,6 @@ import { OTPInput } from 'src/app/features/onboarding/scan/OTPInput' ...@@ -35,7 +35,6 @@ import { OTPInput } from 'src/app/features/onboarding/scan/OTPInput'
import { ScanToOnboard } from 'src/app/features/onboarding/scan/ScanToOnboard' import { ScanToOnboard } from 'src/app/features/onboarding/scan/ScanToOnboard'
import { ScantasticContextProvider } from 'src/app/features/onboarding/scan/ScantasticContextProvider' import { ScantasticContextProvider } from 'src/app/features/onboarding/scan/ScantasticContextProvider'
import { OnboardingRoutes, TopLevelRoutes } from 'src/app/navigation/constants' 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 { 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'
...@@ -140,19 +139,14 @@ const allRoutes = [ ...@@ -140,19 +139,14 @@ const allRoutes = [
}, },
] ]
const router = createHashRouter( const router = createHashRouter([
[
{
path: `/${TopLevelRoutes.Onboarding}`,
element: <OnboardingWrapper />,
errorElement: <ErrorElement />,
children: !supportsSidePanel ? [unsupportedRoute] : allRoutes,
},
],
{ {
future: ROUTER_FUTURE_FLAGS, path: `/${TopLevelRoutes.Onboarding}`,
element: <OnboardingWrapper />,
errorElement: <ErrorElement />,
children: !supportsSidePanel ? [unsupportedRoute] : allRoutes,
}, },
) ])
function ScantasticFlow({ isResetting = false }: { isResetting?: boolean }): JSX.Element { function ScantasticFlow({ isResetting = false }: { isResetting?: boolean }): JSX.Element {
return ( return (
...@@ -199,7 +193,7 @@ export default function OnboardingApp(): JSX.Element { ...@@ -199,7 +193,7 @@ export default function OnboardingApp(): JSX.Element {
<PersistGate persistor={getReduxPersistor()}> <PersistGate persistor={getReduxPersistor()}>
<BaseAppContainer appName={DatadogAppNameTag.Onboarding}> <BaseAppContainer appName={DatadogAppNameTag.Onboarding}>
<PrimaryAppInstanceDebuggerLazy /> <PrimaryAppInstanceDebuggerLazy />
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} /> <RouterProvider router={router} />
</BaseAppContainer> </BaseAppContainer>
</PersistGate> </PersistGate>
) )
......
...@@ -3,11 +3,10 @@ import 'src/app/Global.css' ...@@ -3,11 +3,10 @@ import 'src/app/Global.css'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useTranslation } from 'react-i18next' 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 { 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 { ROUTER_FUTURE_FLAGS, ROUTER_PROVIDER_FUTURE_FLAGS } from 'src/app/navigation/routerConfig'
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 { UNISWAP_LOGO } from 'ui/src/assets'
...@@ -18,18 +17,13 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants' ...@@ -18,18 +17,13 @@ import { ElementName } from 'uniswap/src/features/telemetry/constants'
import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { ExtensionScreens } from 'uniswap/src/types/screens/extension'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics' import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
const router = createHashRouter( const router = createHashRouter([
[
{
path: '',
element: <PopupContent />,
errorElement: <ErrorElement />,
},
],
{ {
future: ROUTER_FUTURE_FLAGS, path: '',
element: <PopupContent />,
errorElement: <ErrorElement />,
}, },
) ])
function PopupContent(): JSX.Element { function PopupContent(): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -108,7 +102,7 @@ export default function PopupApp(): JSX.Element { ...@@ -108,7 +102,7 @@ export default function PopupApp(): JSX.Element {
return ( return (
<BaseAppContainer appName={DatadogAppNameTag.Popup}> <BaseAppContainer appName={DatadogAppNameTag.Popup}>
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} /> <RouterProvider router={router} />
</BaseAppContainer> </BaseAppContainer>
) )
} }
...@@ -4,7 +4,7 @@ import 'src/app/Global.css' ...@@ -4,7 +4,7 @@ import 'src/app/Global.css'
import { SharedEventName } from '@uniswap/analytics-events' import { SharedEventName } from '@uniswap/analytics-events'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useDispatch } from 'react-redux' 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 { PersistGate } from 'redux-persist/integration/react'
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'
...@@ -29,7 +29,6 @@ import { SwapFlowScreen } from 'src/app/features/swap/SwapFlowScreen' ...@@ -29,7 +29,6 @@ import { SwapFlowScreen } from 'src/app/features/swap/SwapFlowScreen'
import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked' import { useIsWalletUnlocked } from 'src/app/hooks/useIsWalletUnlocked'
import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants' import { AppRoutes, RemoveRecoveryPhraseRoutes, SettingsRoutes } from 'src/app/navigation/constants'
import { MainContent, WebNavigation } from 'src/app/navigation/navigation' 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 { setRouter, setRouterState } from 'src/app/navigation/state'
import { initExtensionAnalytics } from 'src/app/utils/analytics' import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { import {
...@@ -49,93 +48,88 @@ import { useInterval } from 'utilities/src/time/timing' ...@@ -49,93 +48,88 @@ 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' import { getReduxPersistor } from 'wallet/src/state/persistor'
const router = createHashRouter( 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 />,
},
],
},
],
{ {
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 const PORT_PING_INTERVAL = 5 * ONE_SECOND_MS
function useDappRequestPortListener(): void { function useDappRequestPortListener(): void {
...@@ -252,7 +246,7 @@ export default function SidebarApp(): JSX.Element { ...@@ -252,7 +246,7 @@ export default function SidebarApp(): JSX.Element {
<BaseAppContainer appName={DatadogAppNameTag.Sidebar}> <BaseAppContainer appName={DatadogAppNameTag.Sidebar}>
<DappContextProvider> <DappContextProvider>
<PrimaryAppInstanceDebuggerLazy /> <PrimaryAppInstanceDebuggerLazy />
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} /> <RouterProvider router={router} />
</DappContextProvider> </DappContextProvider>
</BaseAppContainer> </BaseAppContainer>
</PersistGate> </PersistGate>
......
...@@ -2,7 +2,7 @@ import '@tamagui/core/reset.css' ...@@ -2,7 +2,7 @@ import '@tamagui/core/reset.css'
import 'src/app/Global.css' import 'src/app/Global.css'
import { PropsWithChildren, useEffect } from 'react' 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 { 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'
...@@ -19,7 +19,6 @@ import { UnitagConfirmationScreen } from 'src/app/features/unitags/UnitagConfirm ...@@ -19,7 +19,6 @@ import { UnitagConfirmationScreen } from 'src/app/features/unitags/UnitagConfirm
import { UnitagCreateUsernameScreen } from 'src/app/features/unitags/UnitagCreateUsernameScreen' import { UnitagCreateUsernameScreen } from 'src/app/features/unitags/UnitagCreateUsernameScreen'
import { UnitagIntroScreen } from 'src/app/features/unitags/UnitagIntroScreen' import { UnitagIntroScreen } from 'src/app/features/unitags/UnitagIntroScreen'
import { UnitagClaimRoutes } from 'src/app/navigation/constants' 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 { setRouter, setRouterState } from 'src/app/navigation/state'
import { initExtensionAnalytics } from 'src/app/utils/analytics' import { initExtensionAnalytics } from 'src/app/utils/analytics'
import { Flex } from 'ui/src' import { Flex } from 'ui/src'
...@@ -28,29 +27,24 @@ import { usePrevious } from 'utilities/src/react/hooks' ...@@ -28,29 +27,24 @@ import { usePrevious } from 'utilities/src/react/hooks'
import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics' import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks/useTestnetModeForLoggingAndAnalytics'
import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks' import { useAccountAddressFromUrlWithThrow } from 'wallet/src/features/wallet/hooks'
const router = createHashRouter( const router = createHashRouter([
[
{
path: '',
element: <UnitagAppInner />,
children: [
{
path: UnitagClaimRoutes.ClaimIntro,
element: <UnitagClaimFlow />,
errorElement: <ErrorElement />,
},
{
path: UnitagClaimRoutes.EditProfile,
element: <UnitagEditProfileFlow />,
errorElement: <ErrorElement />,
},
],
},
],
{ {
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 * Note: we are using a pattern here to avoid circular dependencies, because
...@@ -144,7 +138,7 @@ export default function UnitagClaimApp(): JSX.Element { ...@@ -144,7 +138,7 @@ export default function UnitagClaimApp(): JSX.Element {
return ( return (
<BaseAppContainer appName={DatadogAppNameTag.UnitagClaim}> <BaseAppContainer appName={DatadogAppNameTag.UnitagClaim}>
<RouterProvider router={router} future={ROUTER_PROVIDER_FUTURE_FLAGS} /> <RouterProvider router={router} />
</BaseAppContainer> </BaseAppContainer>
) )
} }
...@@ -236,7 +236,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -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);" 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 <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 Edit label
</span> </span>
...@@ -530,7 +531,8 @@ exports[`AccountSwitcherScreen renders correctly 1`] = ` ...@@ -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);" 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 <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 Edit label
</span> </span>
......
...@@ -71,7 +71,7 @@ export function WrapRequestContent({ ...@@ -71,7 +71,7 @@ export function WrapRequestContent({
// Parse the amount from the data field (remove the function selector - first 10 characters including 0x) // Parse the amount from the data field (remove the function selector - first 10 characters including 0x)
const data = dappRequest.transaction.data.toString() const data = dappRequest.transaction.data.toString()
if (data.length > 10) { 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 { } else {
// For wrap, amount is in the value field // For wrap, amount is in the value field
......
...@@ -61,13 +61,9 @@ function SendCallsRequestContent({ ...@@ -61,13 +61,9 @@ function SendCallsRequestContent({
export function SendCallsRequestHandler({ request }: { request: DappRequestStoreItemForSendCallsTxn }): JSX.Element { export function SendCallsRequestHandler({ request }: { request: DappRequestStoreItemForSendCallsTxn }): JSX.Element {
const { dappUrl, onConfirm, onCancel } = useDappRequestQueueContext() const { dappUrl, onConfirm, onCancel } = useDappRequestQueueContext()
const chainId = useDappLastChainId(dappUrl) const chainId = useDappLastChainId(dappUrl) ?? request.dappInfo?.lastChainId
const activeAccountAddress = useActiveAccountAddressWithThrow() const activeAccountAddress = useActiveAccountAddressWithThrow()
if (!chainId) {
throw new Error('Chain ID is required')
}
const { dappRequest } = request const { dappRequest } = request
const parsedSwapCalldata = useMemo(() => { const parsedSwapCalldata = useMemo(() => {
...@@ -81,11 +77,13 @@ export function SendCallsRequestHandler({ request }: { request: DappRequestStore ...@@ -81,11 +77,13 @@ export function SendCallsRequestHandler({ request }: { request: DappRequestStore
const { data: encoded7702data } = useWalletEncode7702Query({ const { data: encoded7702data } = useWalletEncode7702Query({
enabled: !!chainId, enabled: !!chainId,
params: { params: {
calls: transformCallsToTransactionRequests({ calls: chainId
calls: dappRequest.calls, ? transformCallsToTransactionRequests({
chainId, calls: dappRequest.calls,
accountAddress: activeAccountAddress, chainId,
}), accountAddress: activeAccountAddress,
})
: [],
smartContractDelegationAddress: UNISWAP_DELEGATION_ADDRESS, smartContractDelegationAddress: UNISWAP_DELEGATION_ADDRESS,
// @ts-ignore - walletAddress is needed for the API but not in the type yet // @ts-ignore - walletAddress is needed for the API but not in the type yet
// TODO: remove this once the API is updated // TODO: remove this once the API is updated
......
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import { Provider, TransactionResponse } from '@ethersproject/providers' import { Provider, TransactionResponse } from '@ethersproject/providers'
import { providerErrors, rpcErrors, serializeError } from '@metamask/rpc-errors' 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 { changeChain } from 'src/app/features/dapp/changeChain'
import { DappInfo, dappStore } from 'src/app/features/dapp/store' import { DappInfo, dappStore } from 'src/app/features/dapp/store'
import { getActiveConnectedAccount } from 'src/app/features/dapp/utils' import { getActiveConnectedAccount } from 'src/app/features/dapp/utils'
......
...@@ -81,7 +81,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J ...@@ -81,7 +81,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
screen: ExtensionScreens.Home, screen: ExtensionScreens.Home,
element: ElementName.Send, element: ElementName.Send,
}) })
navigate(AppRoutes.Send) navigate(`/${AppRoutes.Send}`)
} }
const onSwapClick = (): void => { const onSwapClick = (): void => {
...@@ -89,7 +89,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J ...@@ -89,7 +89,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
screen: ExtensionScreens.Home, screen: ExtensionScreens.Home,
element: ElementName.Swap, element: ElementName.Swap,
}) })
navigate(AppRoutes.Swap) navigate(`/${AppRoutes.Swap}`)
} }
const onReceiveClick = (): void => { const onReceiveClick = (): void => {
...@@ -97,7 +97,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J ...@@ -97,7 +97,7 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J
screen: ExtensionScreens.Home, screen: ExtensionScreens.Home,
element: ElementName.Receive, element: ElementName.Receive,
}) })
navigate(AppRoutes.Receive) navigate(`/${AppRoutes.Receive}`)
} }
const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false) const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false)
......
...@@ -112,7 +112,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf ...@@ -112,7 +112,7 @@ export const PortfolioHeader = memo(function _PortfolioHeader({ address }: Portf
const onPressAccount = async (): Promise<void> => { const onPressAccount = async (): Promise<void> => {
dispatch(closePopup(PopupName.Connect)) dispatch(closePopup(PopupName.Connect))
navigate(AppRoutes.AccountSwitcher) navigate(`/${AppRoutes.AccountSwitcher}`)
} }
const { isConnected, lastChainId, dappUrl, dappIconUrl } = useDappContext() const { isConnected, lastChainId, dappUrl, dappIconUrl } = useDappContext()
......
...@@ -60,7 +60,7 @@ function TokenBalanceListInner(): JSX.Element { ...@@ -60,7 +60,7 @@ function TokenBalanceListInner(): JSX.Element {
} }
const onPressReceive = (): void => { const onPressReceive = (): void => {
navigate(AppRoutes.Receive) navigate(`/${AppRoutes.Receive}`)
} }
const hasData = !!balancesById const hasData = !!balancesById
......
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Outlet } from 'react-router-dom' import { Outlet } from 'react-router'
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'
import { ONBOARDING_BACKGROUND_DARK, ONBOARDING_BACKGROUND_LIGHT } from 'src/assets' import { ONBOARDING_BACKGROUND_DARK, ONBOARDING_BACKGROUND_LIGHT } from 'src/assets'
......
import { PropsWithChildren } from 'react' import { PropsWithChildren } from 'react'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
import { Link, LinkProps } from 'react-router-dom' import { Link, LinkProps } from 'react-router'
import { Text } from 'ui/src' import { Text } from 'ui/src'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
......
import { Action, AuthenticationTypes } from '@uniswap/client-embeddedwallet/dist/uniswap/embeddedwallet/v1/service_pb' import { Action, AuthenticationTypes } from '@uniswap/client-embeddedwallet/dist/uniswap/embeddedwallet/v1/service_pb'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' 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 { useOnboardingSteps } from 'src/app/features/onboarding/OnboardingStepsContext'
import { usePasskeyImportContext } from 'src/app/features/onboarding/import/PasskeyImportContextProvider' import { usePasskeyImportContext } from 'src/app/features/onboarding/import/PasskeyImportContextProvider'
import { import {
......
import { useTranslation } from 'react-i18next' 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 { OptionCard } from 'src/app/components/buttons/OptionCard'
import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen' import { OnboardingScreen } from 'src/app/features/onboarding/OnboardingScreen'
import { import {
......
import { useTranslation } from 'react-i18next' 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 { 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 { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux' 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 { useDappContext } from 'src/app/features/dapp/DappContext'
import { removeDappConnection } from 'src/app/features/dapp/actions' import { removeDappConnection } from 'src/app/features/dapp/actions'
import { SwitchNetworksModal } from 'src/app/features/home/SwitchNetworksModal' import { SwitchNetworksModal } from 'src/app/features/home/SwitchNetworksModal'
......
...@@ -6015,7 +6015,8 @@ exports[`ReceiveScreen renders without error 1`] = ` ...@@ -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);" 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 <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 Networks
</span> </span>
...@@ -12051,7 +12052,8 @@ exports[`ReceiveScreen renders without error 1`] = ` ...@@ -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);" 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 <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 Networks
</span> </span>
......
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Button, Flex } from 'ui/src' import { Button, Flex } from 'ui/src'
import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types' 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 Trace from 'uniswap/src/features/telemetry/Trace'
import { ElementName } from 'uniswap/src/features/telemetry/constants' 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 { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { isWeb } from 'utilities/src/platform' import { isWeb } from 'utilities/src/platform'
import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext'
...@@ -21,7 +21,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem ...@@ -21,7 +21,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem
derivedSendInfo: { chainId }, derivedSendInfo: { chainId },
} = useSendContext() } = useSendContext()
const nativeCurrencySymbol = NativeCurrency.onChain(chainId).symbol const nativeCurrencySymbol = nativeOnChain(chainId).symbol
const insufficientGasFunds = warnings.warnings.some((warning) => warning.type === WarningLabel.InsufficientGasFunds) const insufficientGasFunds = warnings.warnings.some((warning) => warning.type === WarningLabel.InsufficientGasFunds)
...@@ -29,7 +29,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem ...@@ -29,7 +29,7 @@ export function ReviewButton({ onPress, disabled }: ReviewButtonProps): JSX.Elem
const buttonText = insufficientGasFunds const buttonText = insufficientGasFunds
? t('send.warning.insufficientFunds.title', { ? t('send.warning.insufficientFunds.title', {
currencySymbol: nativeCurrencySymbol, currencySymbol: nativeCurrencySymbol ?? '',
}) })
: t('common.button.review') : t('common.button.review')
......
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom' import { Link } from 'react-router'
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
......
...@@ -121,7 +121,7 @@ export function ViewRecoveryPhraseScreen({ ...@@ -121,7 +121,7 @@ export function ViewRecoveryPhraseScreen({
emphasis="secondary" emphasis="secondary"
onPress={(): void => onPress={(): void =>
navigate( navigate(
`${AppRoutes.Settings}/${SettingsRoutes.RemoveRecoveryPhrase}/${RemoveRecoveryPhraseRoutes.Wallets}`, `/${AppRoutes.Settings}/${SettingsRoutes.RemoveRecoveryPhrase}/${RemoveRecoveryPhraseRoutes.Wallets}`,
{ replace: true }, { replace: true },
) )
} }
......
import { Outlet } from 'react-router-dom' import { Outlet } from 'react-router'
import { Flex } from 'ui/src' 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 { ColorTokens, Flex, GeneratedIcon, Text, TouchableArea, useSporeColors } from 'ui/src'
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'
......
...@@ -26,7 +26,7 @@ export function EditUnitagProfileScreen({ enableBack = false }: { enableBack?: b ...@@ -26,7 +26,7 @@ export function EditUnitagProfileScreen({ enableBack = false }: { enableBack?: b
useEffect(() => { useEffect(() => {
if (!pending && !fetching && !unitag) { if (!pending && !fetching && !unitag) {
navigate(UnitagClaimRoutes.ClaimIntro) navigate(`/${UnitagClaimRoutes.ClaimIntro}`)
} }
}, [unitag, pending, fetching]) }, [unitag, pending, fetching])
......
...@@ -21,7 +21,7 @@ export function UnitagIntroScreen(): JSX.Element { ...@@ -21,7 +21,7 @@ export function UnitagIntroScreen(): JSX.Element {
useEffect(() => { useEffect(() => {
if (unitag?.address) { if (unitag?.address) {
navigate(UnitagClaimRoutes.EditProfile) navigate(`/${UnitagClaimRoutes.EditProfile}`)
} }
}, [unitag]) }, [unitag])
......
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { createSearchParams } from 'react-router-dom' import { createSearchParams } from 'react-router'
import { getRouter } from 'src/app/navigation/state' import { getRouter } from 'src/app/navigation/state'
import { sleep } from 'utilities/src/time/timing' import { sleep } from 'utilities/src/time/timing'
...@@ -14,7 +14,7 @@ export function useOptimizedSearchParams(): URLSearchParams { ...@@ -14,7 +14,7 @@ export function useOptimizedSearchParams(): URLSearchParams {
useEffect(() => { useEffect(() => {
return getRouter().subscribe(async () => { 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) await sleep(0)
setSearchParams((prev) => { setSearchParams((prev) => {
const next = getSearchParams() const next = getSearchParams()
......
import { PropsWithChildren, useCallback } from 'react' 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 { navigateToInterfaceFiatOnRamp } from 'src/app/features/for/utils'
import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants' import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
...@@ -123,7 +123,7 @@ function useNavigateToAccountActivityList(): () => void { ...@@ -123,7 +123,7 @@ function useNavigateToAccountActivityList(): () => void {
const navigateFix = useNavigate() const navigateFix = useNavigate()
return useCallback( return useCallback(
(): void => (): void | Promise<void> =>
navigateFix({ navigateFix({
pathname: AppRoutes.Home, pathname: AppRoutes.Home,
search: createSearchParams({ search: createSearchParams({
...@@ -139,7 +139,7 @@ function useNavigateToAccountTokenList(): () => void { ...@@ -139,7 +139,7 @@ function useNavigateToAccountTokenList(): () => void {
const navigateFix = useNavigate() const navigateFix = useNavigate()
return useCallback( return useCallback(
(): void => (): void | Promise<void> =>
navigateFix({ navigateFix({
pathname: AppRoutes.Home, pathname: AppRoutes.Home,
search: createSearchParams({ search: createSearchParams({
...@@ -151,7 +151,7 @@ function useNavigateToAccountTokenList(): () => void { ...@@ -151,7 +151,7 @@ function useNavigateToAccountTokenList(): () => void {
} }
function useNavigateToReceive(): () => void { function useNavigateToReceive(): () => void {
return useCallback((): void => navigate(AppRoutes.Receive), []) return useCallback((): void => navigate(`/${AppRoutes.Receive}`), [])
} }
function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void { function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void {
...@@ -160,7 +160,7 @@ function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void { ...@@ -160,7 +160,7 @@ function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void {
const state: SidebarLocationState = args ? { initialTransactionState: initialState } : undefined const state: SidebarLocationState = args ? { initialTransactionState: initialState } : undefined
navigate(AppRoutes.Send, { state }) navigate(`/${AppRoutes.Send}`, { state })
}, []) }, [])
} }
...@@ -172,7 +172,7 @@ function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void { ...@@ -172,7 +172,7 @@ function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void {
const state: SidebarLocationState = initialState ? { initialTransactionState: initialState } : undefined const state: SidebarLocationState = initialState ? { initialTransactionState: initialState } : undefined
navigate(AppRoutes.Swap, { state }) navigate(`/${AppRoutes.Swap}`, { state })
}, },
[defaultChainId], [defaultChainId],
) )
......
import { useMutation } from '@tanstack/react-query' import { useMutation } from '@tanstack/react-query'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { NavigationType, Outlet, ScrollRestoration, useLocation } from 'react-router-dom' import { NavigationType, Outlet, ScrollRestoration, useLocation } from 'react-router'
import { SmartWalletNudgeModals } from 'src/app/components/modals/SmartWalletNudgeModals' 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'
......
// 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 { useEffect, useState } from 'react'
import { Location, NavigationType, Router, createHashRouter } from 'react-router-dom' import { Location, NavigationType, Router, createHashRouter } from 'react-router'
interface RouterState { interface RouterState {
historyAction: NavigationType historyAction: NavigationType
...@@ -52,7 +52,7 @@ export function useRouterState(): RouterState | null { ...@@ -52,7 +52,7 @@ export function useRouterState(): RouterState | null {
return val 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> type Router = ReturnType<typeof createHashRouter>
let router: Router | null = null 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 { TopLevelRoutes, UnitagClaimRoutes } from 'src/app/navigation/constants'
import { navigate } from 'src/app/navigation/state' import { navigate } from 'src/app/navigation/state'
import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels' import { onboardingMessageChannel } from 'src/background/messagePassing/messageChannels'
......
...@@ -39,6 +39,7 @@ import { ...@@ -39,6 +39,7 @@ import {
import { logContentScriptError } from 'src/contentScript/utils' import { logContentScriptError } from 'src/contentScript/utils'
import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils' import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils'
import { EthMethod } from 'uniswap/src/features/dappRequests/types' 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 { ExtensionEventName } from 'uniswap/src/features/telemetry/constants'
import { getValidAddress } from 'uniswap/src/utils/addresses' import { getValidAddress } from 'uniswap/src/utils/addresses'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -88,7 +89,7 @@ const setChainIdAndMaybeEmit = (newChainId: string): void => { ...@@ -88,7 +89,7 @@ const setChainIdAndMaybeEmit = (newChainId: string): void => {
const setConnectedAddressesAndMaybeEmit = (newConnectedAddresses: Address[]): void => { const setConnectedAddressesAndMaybeEmit = (newConnectedAddresses: Address[]): void => {
// Only emit if the addresses have changed, and it's not the first time // Only emit if the addresses have changed, and it's not the first time
const normalizedNewAddresses: Address[] = newConnectedAddresses const normalizedNewAddresses: Address[] = newConnectedAddresses
.map((address) => getValidAddress({ address })) .map((address) => getValidAddress({ address, platform: Platform.EVM }))
.filter((normalizedAddress): normalizedAddress is Address => normalizedAddress !== null) .filter((normalizedAddress): normalizedAddress is Address => normalizedAddress !== null)
if (!connectedAddresses || !arraysAreEqual(connectedAddresses, normalizedNewAddresses)) { if (!connectedAddresses || !arraysAreEqual(connectedAddresses, normalizedNewAddresses)) {
......
...@@ -15,6 +15,7 @@ const POLL_ENV = process.env.WEBPACK_POLLING_INTERVAL ...@@ -15,6 +15,7 @@ const POLL_ENV = process.env.WEBPACK_POLLING_INTERVAL
process.env.NODE_ENV = NODE_ENV process.env.NODE_ENV = NODE_ENV
const isDevelopment = NODE_ENV === 'development' const isDevelopment = NODE_ENV === 'development'
const isProduction = NODE_ENV === 'production'
const appDirectory = path.resolve(__dirname) const appDirectory = path.resolve(__dirname)
const manifest = require('./src/manifest.json') const manifest = require('./src/manifest.json')
...@@ -283,6 +284,12 @@ module.exports = (env) => { ...@@ -283,6 +284,12 @@ module.exports = (env) => {
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'), '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 // Add support for web-based extensions so we can share code between mobile/extension
extensions: [ extensions: [
......
...@@ -4,7 +4,8 @@ gem 'fastlane', '2.214.0' ...@@ -4,7 +4,8 @@ gem 'fastlane', '2.214.0'
# Exclude problematic versions of cocoapods and activesupport that causes build failures. # Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '1.14.3' gem 'cocoapods', '1.14.3'
gem 'activesupport', '7.1.2' 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') plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path) eval_gemfile(plugins_path) if File.exist?(plugins_path)
...@@ -81,7 +81,7 @@ GEM ...@@ -81,7 +81,7 @@ GEM
colored2 (3.1.2) colored2 (3.1.2)
commander (4.6.0) commander (4.6.0)
highline (~> 2.0.0) highline (~> 2.0.0)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.4)
connection_pool (2.5.0) connection_pool (2.5.0)
declarative (0.0.20) declarative (0.0.20)
digest-crc (0.6.5) digest-crc (0.6.5)
...@@ -222,7 +222,7 @@ GEM ...@@ -222,7 +222,7 @@ GEM
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.3.0) multipart-post (2.3.0)
mutex_m (0.3.0) mutex_m (0.3.0)
nanaimo (0.3.0) nanaimo (0.4.0)
nap (1.1.0) nap (1.1.0)
naturally (2.2.1) naturally (2.2.1)
netrc (0.11.0) netrc (0.11.0)
...@@ -236,7 +236,7 @@ GEM ...@@ -236,7 +236,7 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
retriable (3.1.2) retriable (3.1.2)
rexml (3.2.6) rexml (3.4.1)
rouge (2.0.7) rouge (2.0.7)
ruby-macho (2.5.1) ruby-macho (2.5.1)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
...@@ -266,13 +266,13 @@ GEM ...@@ -266,13 +266,13 @@ GEM
unicode-display_width (1.8.0) unicode-display_width (1.8.0)
webrick (1.8.1) webrick (1.8.1)
word_wrap (1.0.0) word_wrap (1.0.0)
xcodeproj (1.23.0) xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0) CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3) atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1) colored2 (~> 3.1)
nanaimo (~> 0.3.0) nanaimo (~> 0.4.0)
rexml (~> 3.2.4) rexml (>= 3.3.6, < 4.0)
xcpretty (0.3.0) xcpretty (0.3.0)
rouge (~> 2.0.7) rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1) xcpretty-travis-formatter (1.0.1)
...@@ -284,10 +284,11 @@ PLATFORMS ...@@ -284,10 +284,11 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activesupport (= 7.1.2) activesupport (= 7.1.2)
cocoapods (= 1.14.3) cocoapods (= 1.14.3)
concurrent-ruby (= 1.3.4)
fastlane (= 2.214.0) fastlane (= 2.214.0)
fastlane-plugin-get_version_name fastlane-plugin-get_version_name
fastlane-plugin-versioning_android fastlane-plugin-versioning_android
xcodeproj (= 1.26.0) xcodeproj (= 1.27.0)
BUNDLED WITH BUNDLED WITH
2.4.10 2.4.10
...@@ -6,6 +6,7 @@ plugins { ...@@ -6,6 +6,7 @@ plugins {
id 'com.google.gms.google-services' id 'com.google.gms.google-services'
id 'maven-publish' id 'maven-publish'
id 'kotlin-android' id 'kotlin-android'
id 'org.jetbrains.kotlin.plugin.compose'
} }
def nodeModulesPath = "../../../../node_modules" def nodeModulesPath = "../../../../node_modules"
......
...@@ -6,8 +6,8 @@ buildscript { ...@@ -6,8 +6,8 @@ buildscript {
minSdkVersion = 28 minSdkVersion = 28
compileSdkVersion = 35 compileSdkVersion = 35
targetSdkVersion = 35 targetSdkVersion = 35
ndkVersion = "26.1.10909125" ndkVersion = "27.1.12297006"
kotlinVersion = "1.9.25" kotlinVersion = "2.0.21"
appCompat = "1.6.1" appCompat = "1.6.1"
compose = "1.4.3" compose = "1.4.3"
...@@ -33,6 +33,7 @@ buildscript { ...@@ -33,6 +33,7 @@ buildscript {
plugins { plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion" id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlinVersion"
id 'org.jetbrains.kotlin.plugin.compose' version "$kotlinVersion" apply false
} }
allprojects { allprojects {
......
This diff is collapsed.
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#import "Uniswap-Swift.h" #import "Uniswap-Swift.h"
#import <React/RCTBundleURLProvider.h> #import <React/RCTBundleURLProvider.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
#import <ReactNativePerformance/ReactNativePerformance.h> #import <ReactNativePerformance/ReactNativePerformance.h>
#import <RCTAppSetupUtils.h> #import <RCTAppSetupUtils.h>
#import <RNBootSplash.h> #import <RNBootSplash.h>
...@@ -56,6 +57,7 @@ static NSString *const hasLaunchedOnceKey = @"HasLaunchedOnce"; ...@@ -56,6 +57,7 @@ static NSString *const hasLaunchedOnceKey = @"HasLaunchedOnce";
} }
self.moduleName = @"Uniswap"; self.moduleName = @"Uniswap";
self.dependencyProvider = [RCTAppDependencyProvider new];
self.initialProps = @{}; self.initialProps = @{};
[self.window makeKeyAndVisible]; [self.window makeKeyAndVisible];
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
"check:deps:usage": "./scripts/checkDepsUsage.sh", "check:deps:usage": "./scripts/checkDepsUsage.sh",
"check:bundlesize": "./scripts/checkBundleSize.sh", "check:bundlesize": "./scripts/checkBundleSize.sh",
"clean": "react-native-clean-project", "clean": "react-native-clean-project",
"debug": "react-devtools",
"debug:reactotron:install": "./scripts/installDebugger.sh", "debug:reactotron:install": "./scripts/installDebugger.sh",
"deduplicate": "yarn-deduplicate --strategy=fewer", "deduplicate": "yarn-deduplicate --strategy=fewer",
"depcheck": "depcheck", "depcheck": "depcheck",
...@@ -31,6 +30,7 @@ ...@@ -31,6 +30,7 @@
"link:assets": "react-native-asset", "link:assets": "react-native-asset",
"check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 0", "check:circular": "../../scripts/check-circular-imports.sh ./src/app/App.tsx 0",
"ios": "rnef run:ios --scheme Uniswap --configuration Debug && yarn start", "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:smol": "rnef run:ios --device=\"iPhone SE (3rd generation)\"",
"ios:dev:release": "rnef run:ios --configuration Dev", "ios:dev:release": "rnef run:ios --configuration Dev",
"ios:beta": "rnef run:ios --configuration Beta", "ios:beta": "rnef run:ios --configuration Beta",
...@@ -72,11 +72,11 @@ ...@@ -72,11 +72,11 @@
"@react-native-firebase/auth": "21.0.0", "@react-native-firebase/auth": "21.0.0",
"@react-native-firebase/firestore": "21.0.0", "@react-native-firebase/firestore": "21.0.0",
"@react-native-masked-view/masked-view": "0.3.2", "@react-native-masked-view/masked-view": "0.3.2",
"@react-native/metro-config": "0.76.9", "@react-native/metro-config": "0.77.2",
"@react-navigation/core": "6.2.2", "@react-navigation/core": "7.9.2",
"@react-navigation/native": "6.0.11", "@react-navigation/native": "7.1.9",
"@react-navigation/native-stack": "6.7.0", "@react-navigation/native-stack": "7.3.13",
"@react-navigation/stack": "6.2.2", "@react-navigation/stack": "7.3.2",
"@reduxjs/toolkit": "1.9.3", "@reduxjs/toolkit": "1.9.3",
"@reown/walletkit": "1.2.3", "@reown/walletkit": "1.2.3",
"@rnef/cli": "0.7.18", "@rnef/cli": "0.7.18",
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
"@shopify/flash-list": "1.7.3", "@shopify/flash-list": "1.7.3",
"@shopify/react-native-performance": "4.1.2", "@shopify/react-native-performance": "4.1.2",
"@shopify/react-native-performance-navigation": "3.0.0", "@shopify/react-native-performance-navigation": "3.0.0",
"@shopify/react-native-skia": "1.7.2", "@shopify/react-native-skia": "1.12.4",
"@sparkfabrik/react-native-idfa-aaid": "1.2.0", "@sparkfabrik/react-native-idfa-aaid": "1.2.0",
"@tanstack/react-query": "5.77.2", "@tanstack/react-query": "5.77.2",
"@testing-library/react-hooks": "8.0.1", "@testing-library/react-hooks": "8.0.1",
...@@ -126,14 +126,14 @@ ...@@ -126,14 +126,14 @@
"react": "18.3.1", "react": "18.3.1",
"react-freeze": "1.0.3", "react-freeze": "1.0.3",
"react-i18next": "14.1.0", "react-i18next": "14.1.0",
"react-native": "0.76.9", "react-native": "0.77.2",
"react-native-appsflyer": "6.13.1", "react-native-appsflyer": "6.13.1",
"react-native-bootsplash": "6.3.1", "react-native-bootsplash": "6.3.1",
"react-native-context-menu-view": "1.15.0", "react-native-context-menu-view": "1.15.0",
"react-native-device-info": "10.11.0", "react-native-device-info": "10.11.0",
"react-native-dotenv": "3.2.0", "react-native-dotenv": "3.2.0",
"react-native-fast-image": "8.6.3", "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-get-random-values": "1.11.0",
"react-native-image-colors": "1.5.2", "react-native-image-colors": "1.5.2",
"react-native-image-picker": "7.1.0", "react-native-image-picker": "7.1.0",
...@@ -147,15 +147,15 @@ ...@@ -147,15 +147,15 @@
"react-native-permissions": "4.1.5", "react-native-permissions": "4.1.5",
"react-native-reanimated": "3.16.7", "react-native-reanimated": "3.16.7",
"react-native-restart": "0.0.27", "react-native-restart": "0.0.27",
"react-native-safe-area-context": "4.12.0", "react-native-safe-area-context": "5.1.0",
"react-native-screens": "4.1.0", "react-native-screens": "4.11.0",
"react-native-sortables": "1.5.1", "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-tab-view": "3.5.2",
"react-native-url-polyfill": "1.3.0", "react-native-url-polyfill": "1.3.0",
"react-native-video": "6.13.0", "react-native-video": "6.13.0",
"react-native-wagmi-charts": "2.5.2", "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-native-widgetkit": "1.0.9",
"react-redux": "8.0.5", "react-redux": "8.0.5",
"redux": "4.2.1", "redux": "4.2.1",
...@@ -186,7 +186,9 @@ ...@@ -186,7 +186,9 @@
"@storybook/react-native": "8.5.2", "@storybook/react-native": "8.5.2",
"@tamagui/babel-plugin": "1.125.17", "@tamagui/babel-plugin": "1.125.17",
"@testing-library/react-native": "13.0.0", "@testing-library/react-native": "13.0.0",
"@types/inquirer": "9.0.8",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/node": "22.13.1",
"@types/react": "18.3.18", "@types/react": "18.3.18",
"@types/redux-mock-store": "1.0.6", "@types/redux-mock-store": "1.0.6",
"@uniswap/eslint-config": "workspace:^", "@uniswap/eslint-config": "workspace:^",
...@@ -198,6 +200,7 @@ ...@@ -198,6 +200,7 @@
"core-js": "2.6.12", "core-js": "2.6.12",
"eslint": "8.44.0", "eslint": "8.44.0",
"expo-modules-core": "2.2.3", "expo-modules-core": "2.2.3",
"inquirer": "12.6.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-expo": "52.0.3", "jest-expo": "52.0.3",
"jest-extended": "4.0.2", "jest-extended": "4.0.2",
...@@ -205,7 +208,6 @@ ...@@ -205,7 +208,6 @@
"madge": "6.1.0", "madge": "6.1.0",
"mockdate": "3.0.5", "mockdate": "3.0.5",
"postinstall-postinstall": "2.1.0", "postinstall-postinstall": "2.1.0",
"react-devtools": "4.28.0",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-native-asset": "2.1.1", "react-native-asset": "2.1.1",
"react-native-clean-project": "4.0.1", "react-native-clean-project": "4.0.1",
...@@ -222,10 +224,12 @@ ...@@ -222,10 +224,12 @@
"expo": { "expo": {
"install": { "install": {
"exclude": [ "exclude": [
"react-native@~0.74.0", "react-native@~0.76.9",
"react-native-reanimated@~3.10.0", "react-native-reanimated@~3.16.7",
"react-native-gesture-handler@~2.16.1", "react-native-gesture-handler@~2.20.0",
"react-native-screens@~3.31.1" "react-native-screens@~4.4.0",
"react-native-safe-area-context@~4.12.0",
"react-native-webview@~13.12.5"
] ]
}, },
"autolinking": { "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 @@ ...@@ -2,7 +2,7 @@
set -e set -e
REQUIRED_XCODE_VERSION="16.3" REQUIRED_XCODE_VERSION="16.4"
UPDATE_REPOS=false UPDATE_REPOS=false
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
......
...@@ -200,8 +200,8 @@ function AppOuter(): JSX.Element | null { ...@@ -200,8 +200,8 @@ function AppOuter(): JSX.Element | null {
loading_time: report.timeToBootJsMillis, loading_time: report.timeToBootJsMillis,
}) })
jsBundleLoadedRef.current = true jsBundleLoadedRef.current = true
} // Note that we are not checking report.interactive here because it's not consistently reported.
if (report.interactive) { // Additionally, we are not tracking interactive the same way @shopify/react-native-performance does.
await DdRum.addTiming(DDRumTiming.ScreenInteractive) await DdRum.addTiming(DDRumTiming.ScreenInteractive)
} }
} }
......
import { PropsWithChildren, useCallback } from 'react' import { PropsWithChildren, useCallback } from 'react'
import { Share } from 'react-native' import { Share } from 'react-native'
import { useDispatch } from 'react-redux' 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 { useAppStackNavigation } from 'src/app/navigation/types'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal' import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
import { closeAllModals, closeModal, openModal } from 'src/features/modals/modalSlice' import { closeAllModals, closeModal, openModal } from 'src/features/modals/modalSlice'
...@@ -195,13 +195,22 @@ function useNavigateToTokenDetails(): (currencyId: string) => void { ...@@ -195,13 +195,22 @@ function useNavigateToTokenDetails(): (currencyId: string) => void {
return useCallback( return useCallback(
(currencyId: string): void => { (currencyId: string): void => {
const isExploreNavigationActuallyFocused = Boolean(
navigationRef.getCurrentRoute()?.name === ModalName.Explore &&
exploreNavigationRef.current &&
exploreNavigationRef.isFocused(),
)
closeKeyboardBeforeCallback(() => { closeKeyboardBeforeCallback(() => {
onClose()
dispatch(closeAllModals()) dispatch(closeAllModals())
if (exploreNavigationRef.current && exploreNavigationRef.isFocused()) { if (isExploreNavigationActuallyFocused) {
exploreNavigationRef.navigate(MobileScreens.TokenDetails, { currencyId }) exploreNavigationRef.navigate(MobileScreens.TokenDetails, { currencyId })
} else { } 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 ...@@ -261,7 +261,13 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
variant="subheading1" variant="subheading1"
/> />
<Flex row px="$spacing12"> <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')} {t('account.wallet.button.manage')}
</Button> </Button>
</Flex> </Flex>
...@@ -277,7 +283,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme ...@@ -277,7 +283,7 @@ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Eleme
<TouchableArea mt="$spacing16" testID={TestID.AccountSwitcherAddWallet} onPress={onPressAddWallet}> <TouchableArea mt="$spacing16" testID={TestID.AccountSwitcherAddWallet} onPress={onPressAddWallet}>
<Flex row alignItems="center" gap="$spacing8" ml="$spacing24"> <Flex row alignItems="center" gap="$spacing8" ml="$spacing24">
<PlusCircle /> <PlusCircle />
<Text color="$neutral1" variant="buttonLabel2"> <Text numberOfLines={1} width="100%" color="$neutral1" variant="buttonLabel2">
{t('account.wallet.button.add')} {t('account.wallet.button.add')}
</Text> </Text>
</Flex> </Flex>
......
...@@ -89,7 +89,64 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -89,7 +89,64 @@ exports[`AccountSwitcher renders correctly 1`] = `
"width": 56, "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>
<View <View
style={ style={
...@@ -357,6 +414,7 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -357,6 +414,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
testID="wallet-settings" testID="wallet-settings"
> >
<Text <Text
line-height-disabled="true"
maxFontSizeMultiplier={1.2} maxFontSizeMultiplier={1.2}
numberOfLines={1} numberOfLines={1}
onBlur={[Function]} onBlur={[Function]}
...@@ -372,7 +430,7 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -372,7 +430,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
"fontFamily": "Basel Grotesk", "fontFamily": "Basel Grotesk",
"fontSize": 17, "fontSize": 17,
"fontWeight": "500", "fontWeight": "500",
"lineHeight": 21.849999999999998, "textAlign": "center",
} }
} }
suppressHighlighting={true} suppressHighlighting={true}
...@@ -422,16 +480,6 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -422,16 +480,6 @@ exports[`AccountSwitcher renders correctly 1`] = `
scrollEventThrottle={0.0001} scrollEventThrottle={0.0001}
stickyHeaderIndices={[]} stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]} viewabilityConfigCallbackPairs={[]}
waitFor={
[
{
"current": null,
},
{
"current": null,
},
]
}
> >
<View /> <View />
</RCTScrollView> </RCTScrollView>
...@@ -602,6 +650,7 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -602,6 +650,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
<Text <Text
allowFontScaling={true} allowFontScaling={true}
maxFontSizeMultiplier={1.2} maxFontSizeMultiplier={1.2}
numberOfLines={1}
style={ style={
{ {
"color": { "color": {
...@@ -614,6 +663,7 @@ exports[`AccountSwitcher renders correctly 1`] = ` ...@@ -614,6 +663,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
"fontSize": 17, "fontSize": 17,
"fontWeight": "500", "fontWeight": "500",
"lineHeight": 19.549999999999997, "lineHeight": 19.549999999999997,
"width": "100%",
} }
} }
suppressHighlighting={true} suppressHighlighting={true}
......
import { NavigationContainer } from '@react-navigation/native' import { DefaultTheme, NavigationContainer, NavigationIndependentTree } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack' import { createNativeStackNavigator } from '@react-navigation/native-stack'
import React from 'react' import React from 'react'
import { navNativeStackOptions } from 'src/app/navigation/navStackOptions' import { navNativeStackOptions } from 'src/app/navigation/navStackOptions'
...@@ -20,40 +20,42 @@ export function ExploreStackNavigator(): JSX.Element { ...@@ -20,40 +20,42 @@ export function ExploreStackNavigator(): JSX.Element {
const colors = useSporeColors() const colors = useSporeColors()
return ( return (
<NavigationContainer <NavigationIndependentTree>
ref={exploreNavigationRef} <NavigationContainer
independent ref={exploreNavigationRef}
theme={{ theme={{
dark: false, ...DefaultTheme,
colors: { dark: false,
primary: 'transparent', colors: {
background: 'transparent', primary: 'transparent',
card: 'transparent', background: 'transparent',
text: 'transparent', card: 'transparent',
border: 'transparent', text: 'transparent',
notification: 'transparent', border: 'transparent',
}, notification: 'transparent',
}} },
onStateChange={stopTracking} }}
onReady={() => startTracking(exploreNavigationRef)} onStateChange={stopTracking}
> onReady={() => startTracking(exploreNavigationRef)}
<HorizontalEdgeGestureTarget />
<ExploreStack.Navigator
initialRouteName={MobileScreens.Explore}
screenOptions={navNativeStackOptions.independentBsm}
> >
<ExploreStack.Screen component={ExploreScreen} name={MobileScreens.Explore} /> <HorizontalEdgeGestureTarget />
<ExploreStack.Group screenOptions={{ contentStyle: { backgroundColor: colors.surface1.val } }}> <ExploreStack.Navigator
<ExploreStack.Screen name={MobileScreens.ExternalProfile}> initialRouteName={MobileScreens.Explore}
{(props): JSX.Element => <ExternalProfileScreen {...props} renderedInModal />} screenOptions={navNativeStackOptions.independentBsm}
</ExploreStack.Screen> >
<ExploreStack.Screen name={MobileScreens.NFTCollection}> <ExploreStack.Screen component={ExploreScreen} name={MobileScreens.Explore} />
{(props): JSX.Element => <NFTCollectionScreen {...props} renderedInModal />} <ExploreStack.Group screenOptions={{ contentStyle: { backgroundColor: colors.surface1.val } }}>
</ExploreStack.Screen> <ExploreStack.Screen name={MobileScreens.ExternalProfile}>
<ExploreStack.Screen component={NFTItemScreen} name={MobileScreens.NFTItem} /> {(props): JSX.Element => <ExternalProfileScreen {...props} renderedInModal />}
<ExploreStack.Screen component={TokenDetailsScreen} name={MobileScreens.TokenDetails} /> </ExploreStack.Screen>
</ExploreStack.Group> <ExploreStack.Screen name={MobileScreens.NFTCollection}>
</ExploreStack.Navigator> {(props): JSX.Element => <NFTCollectionScreen {...props} renderedInModal />}
</NavigationContainer> </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 { createNativeStackNavigator } from '@react-navigation/native-stack'
import { TransitionPresets, createStackNavigator } from '@react-navigation/stack' import { TransitionPresets, createStackNavigator } from '@react-navigation/stack'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
...@@ -196,27 +196,28 @@ function WrappedHomeScreen(props: AppStackScreenProp<MobileScreens.Home>): JSX.E ...@@ -196,27 +196,28 @@ function WrappedHomeScreen(props: AppStackScreenProp<MobileScreens.Home>): JSX.E
export function FiatOnRampStackNavigator(): JSX.Element { export function FiatOnRampStackNavigator(): JSX.Element {
return ( return (
<NavigationContainer <NavigationIndependentTree>
ref={fiatOnRampNavigationRef} <NavigationContainer
independent ref={fiatOnRampNavigationRef}
onReady={() => startTracking(fiatOnRampNavigationRef)} onReady={() => startTracking(fiatOnRampNavigationRef)}
onStateChange={stopTracking} onStateChange={stopTracking}
> >
<HorizontalEdgeGestureTarget /> <HorizontalEdgeGestureTarget />
<FiatOnRampProvider> <FiatOnRampProvider>
<FiatOnRampStack.Navigator <FiatOnRampStack.Navigator
initialRouteName={FiatOnRampScreens.AmountInput} initialRouteName={FiatOnRampScreens.AmountInput}
screenOptions={navNativeStackOptions.independentBsm} screenOptions={navNativeStackOptions.independentBsm}
> >
<FiatOnRampStack.Screen component={FiatOnRampScreen} name={FiatOnRampScreens.AmountInput} /> <FiatOnRampStack.Screen component={FiatOnRampScreen} name={FiatOnRampScreens.AmountInput} />
<FiatOnRampStack.Screen <FiatOnRampStack.Screen
component={FiatOnRampServiceProvidersScreen} component={FiatOnRampServiceProvidersScreen}
name={FiatOnRampScreens.ServiceProviders} name={FiatOnRampScreens.ServiceProviders}
/> />
<FiatOnRampStack.Screen component={FiatOnRampConnectingScreen} name={FiatOnRampScreens.Connecting} /> <FiatOnRampStack.Screen component={FiatOnRampConnectingScreen} name={FiatOnRampScreens.Connecting} />
</FiatOnRampStack.Navigator> </FiatOnRampStack.Navigator>
</FiatOnRampProvider> </FiatOnRampProvider>
</NavigationContainer> </NavigationContainer>
</NavigationIndependentTree>
) )
} }
...@@ -316,7 +317,7 @@ function UnitagStackNavigator(): JSX.Element { ...@@ -316,7 +317,7 @@ function UnitagStackNavigator(): JSX.Element {
screenOptions={{ screenOptions={{
headerMode: 'float', headerMode: 'float',
headerTitle: '', headerTitle: '',
headerBackTitleVisible: false, headerBackButtonDisplayMode: 'minimal',
headerBackImage: renderHeaderBackImage, headerBackImage: renderHeaderBackImage,
headerStatusBarHeight: insets.top + spacing.spacing8, headerStatusBarHeight: insets.top + spacing.spacing8,
headerTransparent: true, headerTransparent: true,
...@@ -390,6 +391,7 @@ export function AppStackNavigator(): JSX.Element { ...@@ -390,6 +391,7 @@ export function AppStackNavigator(): JSX.Element {
<AppStack.Screen component={NFTCollectionScreen} name={MobileScreens.NFTCollection} /> <AppStack.Screen component={NFTCollectionScreen} name={MobileScreens.NFTCollection} />
<AppStack.Screen component={WebViewScreen} name={MobileScreens.WebView} /> <AppStack.Screen component={WebViewScreen} name={MobileScreens.WebView} />
<AppStack.Screen component={SettingsStackGroup} name={MobileScreens.SettingsStack} /> <AppStack.Screen component={SettingsStackGroup} name={MobileScreens.SettingsStack} />
<AppStack.Screen component={ViewPrivateKeysScreen} name={MobileScreens.ViewPrivateKeys} />
<AppStack.Group screenOptions={navNativeStackOptions.presentationModal}> <AppStack.Group screenOptions={navNativeStackOptions.presentationModal}>
<AppStack.Screen component={EducationScreen} name={MobileScreens.Education} /> <AppStack.Screen component={EducationScreen} name={MobileScreens.Education} />
</AppStack.Group> </AppStack.Group>
......
import { createNavigationContainerRef } from '@react-navigation/native' 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 // 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 exploreNavigationRef = createNavigationContainerRef<ExploreStackParamList>()
export const fiatOnRampNavigationRef = createNavigationContainerRef<FiatOnRampStackParamList>() export const fiatOnRampNavigationRef = createNavigationContainerRef<FiatOnRampStackParamList>()
export const navRefs = [exploreNavigationRef, fiatOnRampNavigationRef, navigationRef] export const navRefs = [exploreNavigationRef, fiatOnRampNavigationRef, navigationRef]
/* eslint-disable @typescript-eslint/no-explicit-any */
import { NavigationAction, NavigationState } from '@react-navigation/core' import { NavigationAction, NavigationState } from '@react-navigation/core'
import { navigationRef } from 'src/app/navigation/navigationRef' import { navigationRef } from 'src/app/navigation/navigationRef'
import { RootParamList } from 'src/app/navigation/types' import { RootParamList } from 'src/app/navigation/types'
...@@ -23,9 +24,9 @@ export function navigate<RouteName extends keyof RootParamList>(...args: RootNav ...@@ -23,9 +24,9 @@ export function navigate<RouteName extends keyof RootParamList>(...args: RootNav
return 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 // 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( export function dispatchNavigationAction(
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { EthMethod } from 'uniswap/src/features/dappRequests/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 { EthTransaction } from 'uniswap/src/types/walletConnect'
import { getValidAddress } from 'uniswap/src/utils/addresses' import { getValidAddress } from 'uniswap/src/utils/addresses'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -92,7 +93,11 @@ const getParsedObjectDisplay = ({ ...@@ -92,7 +93,11 @@ const getParsedObjectDisplay = ({
const childValue = obj[objKey] const childValue = obj[objKey]
// Special case for address strings // 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 ( return (
<KeyValueRow key={objKey} objKey={objKey}> <KeyValueRow key={objKey} objKey={objKey}>
<Flex> <Flex>
......
...@@ -8,13 +8,13 @@ import { Flex, SpinningLoader, Text, useIsDarkMode } from 'ui/src' ...@@ -8,13 +8,13 @@ import { Flex, SpinningLoader, Text, useIsDarkMode } from 'ui/src'
import { iconSizes, spacing } from 'ui/src/theme' import { iconSizes, spacing } from 'ui/src/theme'
import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo'
import { NetworkFee } from 'uniswap/src/components/gas/NetworkFee' import { NetworkFee } from 'uniswap/src/components/gas/NetworkFee'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { getChainLabel } from 'uniswap/src/features/chains/utils' import { getChainLabel } from 'uniswap/src/features/chains/utils'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { GasFeeResult } from 'uniswap/src/features/gas/types' import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
import { useOnChainCurrencyBalance } from 'uniswap/src/features/portfolio/api' import { useOnChainCurrencyBalance } from 'uniswap/src/features/portfolio/api'
import { ModalName } from 'uniswap/src/features/telemetry/constants' 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 { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
...@@ -99,7 +99,7 @@ function UwULinkErc20SendModalContent({ ...@@ -99,7 +99,7 @@ function UwULinkErc20SendModalContent({
const { convertFiatAmountFormatted } = useLocalizationContext() const { convertFiatAmountFormatted } = useLocalizationContext()
const { chainId, isStablecoin } = request const { chainId, isStablecoin } = request
const nativeCurrency = NativeCurrency.onChain(chainId) const nativeCurrency = nativeOnChain(chainId)
if (loading || !currencyInfo) { if (loading || !currencyInfo) {
return ( return (
...@@ -153,7 +153,7 @@ function UwULinkErc20SendModalContent({ ...@@ -153,7 +153,7 @@ function UwULinkErc20SendModalContent({
{!hasSufficientGasFunds && ( {!hasSufficientGasFunds && (
<Text color="$statusWarning" pt="$spacing8" textAlign="center" variant="body3"> <Text color="$statusWarning" pt="$spacing8" textAlign="center" variant="body3">
{t('walletConnect.request.error.insufficientFunds', { {t('walletConnect.request.error.insufficientFunds', {
currencySymbol: nativeCurrency.symbol, currencySymbol: nativeCurrency.symbol ?? '',
})} })}
</Text> </Text>
)} )}
......
...@@ -12,9 +12,9 @@ import { ...@@ -12,9 +12,9 @@ import {
import { Flex, Text } from 'ui/src' import { Flex, Text } from 'ui/src'
import { AlertTriangleFilled } from 'ui/src/components/icons' import { AlertTriangleFilled } from 'ui/src/components/icons'
import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { EthMethod } from 'uniswap/src/features/dappRequests/types' import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { GasFeeResult } from 'uniswap/src/features/gas/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 { BlockedAddressWarning } from 'uniswap/src/features/transactions/modals/BlockedAddressWarning'
import { isPrimaryTypePermit } from 'uniswap/src/types/walletConnect' import { isPrimaryTypePermit } from 'uniswap/src/types/walletConnect'
import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { buildCurrencyId } from 'uniswap/src/utils/currencyId'
...@@ -69,7 +69,7 @@ export function WalletConnectRequestModalContent({ ...@@ -69,7 +69,7 @@ export function WalletConnectRequestModalContent({
}: WalletConnectRequestModalContentProps): JSX.Element { }: WalletConnectRequestModalContentProps): JSX.Element {
const chainId = request.chainId const chainId = request.chainId
const permitInfo = getPermitInfo(request) const permitInfo = getPermitInfo(request)
const nativeCurrency = NativeCurrency.onChain(chainId) const nativeCurrency = nativeOnChain(chainId)
const { t } = useTranslation() const { t } = useTranslation()
const { animatedFooterHeight } = useBottomSheetInternal() const { animatedFooterHeight } = useBottomSheetInternal()
...@@ -115,7 +115,7 @@ export function WalletConnectRequestModalContent({ ...@@ -115,7 +115,7 @@ export function WalletConnectRequestModalContent({
<Flex p="$spacing16"> <Flex p="$spacing16">
<Text color="$statusWarning" variant="body2"> <Text color="$statusWarning" variant="body2">
{t('walletConnect.request.error.insufficientFunds', { {t('walletConnect.request.error.insufficientFunds', {
currencySymbol: nativeCurrency.symbol, currencySymbol: nativeCurrency.symbol ?? '',
})} })}
</Text> </Text>
</Flex> </Flex>
......
import { useMemo } from 'react' import { useMemo } from 'react'
import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { GasFeeResult } from 'uniswap/src/features/gas/types' import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils' import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils'
import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' 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' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount'
export function useHasSufficientFunds({ export function useHasSufficientFunds({
...@@ -19,7 +19,7 @@ export function useHasSufficientFunds({ ...@@ -19,7 +19,7 @@ export function useHasSufficientFunds({
value?: string value?: string
}): boolean { }): boolean {
const { defaultChainId } = useEnabledChains() const { defaultChainId } = useEnabledChains()
const nativeCurrency = NativeCurrency.onChain(chainId || defaultChainId) const nativeCurrency = nativeOnChain(chainId || defaultChainId)
const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(chainId ?? defaultChainId, account) const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(chainId ?? defaultChainId, account)
const hasSufficientFunds = useMemo(() => { const hasSufficientFunds = useMemo(() => {
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM, UNISWAP_URL_SCHEME_WALLETCONNECT_AS_PARAM,
UNISWAP_WALLETCONNECT_URL, UNISWAP_WALLETCONNECT_URL,
} from 'src/features/deepLinking/constants' } from 'src/features/deepLinking/constants'
import { Platform } from 'uniswap/src/features/platforms/types/Platform'
import { getValidAddress } from 'uniswap/src/utils/addresses' import { getValidAddress } from 'uniswap/src/utils/addresses'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types' import { ScantasticParams, ScantasticParamsSchema } from 'wallet/src/features/scantastic/types'
...@@ -45,7 +46,12 @@ export async function getSupportedURI( ...@@ -45,7 +46,12 @@ export async function getSupportedURI(
return undefined 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) { if (maybeAddress) {
return { type: URIType.Address, value: maybeAddress } return { type: URIType.Address, value: maybeAddress }
} }
...@@ -123,7 +129,7 @@ function getMetamaskAddress(uri: string): Nullable<string> { ...@@ -123,7 +129,7 @@ function getMetamaskAddress(uri: string): Nullable<string> {
return null 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> // format is uniswap://scantastic?<params>
......
...@@ -47,7 +47,7 @@ describe('PrivateKeySpeedBumpModal', () => { ...@@ -47,7 +47,7 @@ describe('PrivateKeySpeedBumpModal', () => {
const continueButton = screen.getByTestId(TestID.Continue) const continueButton = screen.getByTestId(TestID.Continue)
fireEvent.press(continueButton) fireEvent.press(continueButton)
expect(mockPreventCloseRef.current).toBe(true) expect(mockOnClose).toHaveBeenCalled()
expect(mockNavigation.navigate).toHaveBeenCalledWith(MobileScreens.ViewPrivateKeys) expect(mockNavigation.navigate).toHaveBeenCalledWith(MobileScreens.ViewPrivateKeys)
}) })
}) })
...@@ -20,10 +20,10 @@ export function PrivateKeySpeedBumpModal({ ...@@ -20,10 +20,10 @@ export function PrivateKeySpeedBumpModal({
navigation, navigation,
}: AppStackScreenProp<typeof ModalName.PrivateKeySpeedBumpModal>): JSX.Element | null { }: AppStackScreenProp<typeof ModalName.PrivateKeySpeedBumpModal>): JSX.Element | null {
const colors = useSporeColors() const colors = useSporeColors()
const { onClose, preventCloseRef } = useReactNavigationModal() const { onClose } = useReactNavigationModal()
const onContinue = (): void => { const onContinue = (): void => {
preventCloseRef.current = true onClose()
navigation.navigate(MobileScreens.ViewPrivateKeys) navigation.navigate(MobileScreens.ViewPrivateKeys)
} }
......
...@@ -506,6 +506,7 @@ exports[`PrivateKeySpeedBumpModal renders correctly 1`] = ` ...@@ -506,6 +506,7 @@ exports[`PrivateKeySpeedBumpModal renders correctly 1`] = `
testID="continue" testID="continue"
> >
<Text <Text
line-height-disabled="false"
maxFontSizeMultiplier={1.2} maxFontSizeMultiplier={1.2}
numberOfLines={1} numberOfLines={1}
onBlur={[Function]} onBlur={[Function]}
...@@ -522,6 +523,7 @@ exports[`PrivateKeySpeedBumpModal renders correctly 1`] = ` ...@@ -522,6 +523,7 @@ exports[`PrivateKeySpeedBumpModal renders correctly 1`] = `
"fontSize": 17, "fontSize": 17,
"fontWeight": "500", "fontWeight": "500",
"lineHeight": 21.849999999999998, "lineHeight": 21.849999999999998,
"textAlign": "center",
} }
} }
suppressHighlighting={true} suppressHighlighting={true}
......
...@@ -188,7 +188,7 @@ export function ManageWalletsModal({ route }: AppStackScreenProp<typeof ModalNam ...@@ -188,7 +188,7 @@ export function ManageWalletsModal({ route }: AppStackScreenProp<typeof ModalNam
/> />
</Flex> </Flex>
<Flex row pb="$padding20" pt="$padding12"> <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')} {t('settings.setting.wallet.action.remove')}
</Button> </Button>
</Flex> </Flex>
......
...@@ -110,7 +110,11 @@ export const SettingsRow = memo( ...@@ -110,7 +110,11 @@ export const SettingsRow = memo(
if (onToggle) { if (onToggle) {
return return
} else if (screen) { } 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) { } else if (navigationModal) {
navigate(navigationModal, navigationProps) navigate(navigationModal, navigationProps)
} else if (externalLink) { } else if (externalLink) {
......
...@@ -140,7 +140,64 @@ exports[`AccountCardItem renders correctly 1`] = ` ...@@ -140,7 +140,64 @@ exports[`AccountCardItem renders correctly 1`] = `
"width": 32, "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> </View>
<View <View
......
...@@ -125,7 +125,64 @@ exports[`AccountHeader renders correctly 1`] = ` ...@@ -125,7 +125,64 @@ exports[`AccountHeader renders correctly 1`] = `
"width": 52, "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> </View>
<View <View
......
...@@ -56,16 +56,6 @@ exports[`AccountList renders without error 1`] = ` ...@@ -56,16 +56,6 @@ exports[`AccountList renders without error 1`] = `
scrollEventThrottle={0.0001} scrollEventThrottle={0.0001}
stickyHeaderIndices={[]} stickyHeaderIndices={[]}
viewabilityConfigCallbackPairs={[]} viewabilityConfigCallbackPairs={[]}
waitFor={
[
{
"current": null,
},
{
"current": null,
},
]
}
> >
<View> <View>
<View <View
...@@ -212,7 +202,64 @@ exports[`AccountList renders without error 1`] = ` ...@@ -212,7 +202,64 @@ exports[`AccountList renders without error 1`] = `
"width": 32, "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> </View>
<View <View
......
...@@ -164,7 +164,64 @@ exports[`FavoriteWalletCard renders without error 1`] = ` ...@@ -164,7 +164,64 @@ exports[`FavoriteWalletCard renders without error 1`] = `
"width": 20, "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>
<View <View
style={ style={
......
...@@ -2,9 +2,14 @@ import { act, renderHook } from '@testing-library/react-hooks' ...@@ -2,9 +2,14 @@ import { act, renderHook } from '@testing-library/react-hooks'
import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal' import { useReactNavigationModal } from 'src/components/modals/useReactNavigationModal'
const mockGoBack = jest.fn() const mockGoBack = jest.fn()
const mockIsFocused = jest.fn(() => true)
const mockCanGoBack = jest.fn(() => true)
jest.mock('src/app/navigation/types', () => ({ jest.mock('src/app/navigation/types', () => ({
useAppStackNavigation: jest.fn(() => ({ useAppStackNavigation: jest.fn(() => ({
goBack: mockGoBack, goBack: mockGoBack,
isFocused: mockIsFocused,
canGoBack: mockCanGoBack,
})), })),
})) }))
...@@ -36,4 +41,14 @@ describe('useReactNavigationModal', () => { ...@@ -36,4 +41,14 @@ describe('useReactNavigationModal', () => {
}) })
expect(mockGoBack).not.toHaveBeenCalled() 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(): { ...@@ -19,11 +19,13 @@ export function useReactNavigationModal(): {
const preventCloseRef = useRef(false) const preventCloseRef = useRef(false)
const onClose = useCallback(() => { const onClose = useCallback(() => {
if (preventCloseRef.current) { if (preventCloseRef.current || !navigation.isFocused()) {
return return
} }
preventCloseRef.current = true preventCloseRef.current = true
navigation.goBack() if (navigation.canGoBack()) {
navigation.goBack()
}
}, [navigation]) }, [navigation])
return { return {
......
...@@ -35,7 +35,8 @@ import { ...@@ -35,7 +35,8 @@ import {
setHasPendingSessionError, setHasPendingSessionError,
} from 'src/features/walletConnect/walletConnectSlice' } from 'src/features/walletConnect/walletConnectSlice'
import { call, fork, put, select, take } from 'typed-redux-saga' 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 { getChainLabel } from 'uniswap/src/features/chains/utils'
import { EthMethod } from 'uniswap/src/features/dappRequests/types' import { EthMethod } from 'uniswap/src/features/dappRequests/types'
import { isSelfCallWithData } from 'uniswap/src/features/dappRequests/utils' 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