ci(release): publish latest release

parent 6f8caaf7
* @uniswap/web-admins
IPFS hash of the deployment: # We are back an exciting new update!
- CIDv0: `QmeqY4kue58BB4LG4rseGWe5sav5XjAt2CNKhwpmpCmaKF`
- CIDv1: `bafybeihvefjgw6woi34lg7b5oxbc77nqluqp66eeg2kfqq7tn32rcjcdgy`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). Introducing Unichain: Faster swaps. Lower fees. Unichain is optimized to be the home for cross‑chain liquidity.
You can also access the Uniswap Interface from an IPFS gateway. - Swap instantly.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. - Save up to 95% on fees compared to Ethereum.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org). - Lower costs for creating pools and managing positions.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeihvefjgw6woi34lg7b5oxbc77nqluqp66eeg2kfqq7tn32rcjcdgy.ipfs.dweb.link/
- [ipfs://QmeqY4kue58BB4LG4rseGWe5sav5XjAt2CNKhwpmpCmaKF/](ipfs://QmeqY4kue58BB4LG4rseGWe5sav5XjAt2CNKhwpmpCmaKF/)
## 5.71.0 (2025-02-11)
### Features
* **web:** remove unichain beta toggle - prod (#16168) 6fcd098
Other changes:
- Improved warnings for send transactions to smart contract addresses
- Various bug fixes and performance improvements
\ No newline at end of file
web/5.71.0 mobile/1.45.2
\ No newline at end of file \ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Uniswap Extension", "name": "Uniswap Extension",
"description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.", "description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.",
"version": "1.16.0", "version": "1.15.0",
"minimum_chrome_version": "116", "minimum_chrome_version": "116",
"icons": { "icons": {
"16": "assets/icon16.png", "16": "assets/icon16.png",
......
...@@ -89,9 +89,9 @@ if (isCI && datadogPropertiesAvailable && !isE2E) { ...@@ -89,9 +89,9 @@ if (isCI && datadogPropertiesAvailable && !isE2E) {
apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle" apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle"
} }
def devVersionName = "1.46" def devVersionName = "1.45.2"
def betaVersionName = "1.46" def betaVersionName = "1.45.2"
def prodVersionName = "1.46" def prodVersionName = "1.45.2"
android { android {
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
......
...@@ -76,8 +76,8 @@ class NotificationExtension : OSRemoteNotificationReceivedHandler { ...@@ -76,8 +76,8 @@ class NotificationExtension : OSRemoteNotificationReceivedHandler {
"https://gating.android.wallet.gateway.uniswap.org/v1/statsig-proxy" "https://gating.android.wallet.gateway.uniswap.org/v1/statsig-proxy"
private const val STATSIG_ENVIRONMENT_KEY_TIER = "tier" private const val STATSIG_ENVIRONMENT_KEY_TIER = "tier"
private const val FEATURE_GATE_UNFUNDED_WALLET = "notification_unfunded_wallet" private const val FEATURE_GATE_UNFUNDED_WALLET = "notification_unfunded_wallet_android"
private const val FEATURE_GATE_PRICE_ALERT = "notification_price_alerts" private const val FEATURE_GATE_PRICE_ALERT = "notification_price_alerts_android"
private const val FIELD_NOTIFICATION_TYPE = "notification_type" private const val FIELD_NOTIFICATION_TYPE = "notification_type"
private const val TYPE_UNFUNDED_WALLET_REMINDER = "unfunded_wallet_reminder" private const val TYPE_UNFUNDED_WALLET_REMINDER = "unfunded_wallet_reminder"
......
...@@ -87,6 +87,6 @@ struct Constants { ...@@ -87,6 +87,6 @@ struct Constants {
static let typeUnfundedWallet = "unfunded_wallet_reminder" static let typeUnfundedWallet = "unfunded_wallet_reminder"
static let typePriceAlert = "price_alert" static let typePriceAlert = "price_alert"
static let gateUnfundedWallet = "notification_unfunded_wallet" static let gateUnfundedWallet = "notification_unfunded_wallet_ios"
static let gatePriceAlert = "notification_price_alerts" static let gatePriceAlert = "notification_price_alerts_ios"
} }
...@@ -2234,7 +2234,7 @@ ...@@ -2234,7 +2234,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2287,7 +2287,7 @@ ...@@ -2287,7 +2287,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
...@@ -2340,7 +2340,7 @@ ...@@ -2340,7 +2340,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
...@@ -2393,7 +2393,7 @@ ...@@ -2393,7 +2393,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore;
...@@ -2431,7 +2431,7 @@ ...@@ -2431,7 +2431,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2467,7 +2467,7 @@ ...@@ -2467,7 +2467,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
...@@ -2502,7 +2502,7 @@ ...@@ -2502,7 +2502,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
...@@ -2537,7 +2537,7 @@ ...@@ -2537,7 +2537,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests;
...@@ -2584,7 +2584,7 @@ ...@@ -2584,7 +2584,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2630,7 +2630,7 @@ ...@@ -2630,7 +2630,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
...@@ -2676,7 +2676,7 @@ ...@@ -2676,7 +2676,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
...@@ -2722,7 +2722,7 @@ ...@@ -2722,7 +2722,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
...@@ -2764,7 +2764,7 @@ ...@@ -2764,7 +2764,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -2807,7 +2807,7 @@ ...@@ -2807,7 +2807,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
...@@ -2850,7 +2850,7 @@ ...@@ -2850,7 +2850,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
...@@ -2893,7 +2893,7 @@ ...@@ -2893,7 +2893,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
...@@ -2929,7 +2929,7 @@ ...@@ -2929,7 +2929,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -2967,7 +2967,7 @@ ...@@ -2967,7 +2967,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3168,7 +3168,7 @@ ...@@ -3168,7 +3168,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
...@@ -3213,7 +3213,7 @@ ...@@ -3213,7 +3213,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
...@@ -3324,7 +3324,7 @@ ...@@ -3324,7 +3324,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3396,7 +3396,7 @@ ...@@ -3396,7 +3396,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
...@@ -3507,7 +3507,7 @@ ...@@ -3507,7 +3507,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
...@@ -3579,7 +3579,7 @@ ...@@ -3579,7 +3579,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.46; MARKETING_VERSION = 1.45.2;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
...@@ -256,8 +256,8 @@ function AppOuter(): JSX.Element | null { ...@@ -256,8 +256,8 @@ function AppOuter(): JSX.Element | null {
// Used in case we aren't able to resolve notification filtering issues on iOS // Used in case we aren't able to resolve notification filtering issues on iOS
if (isIOS) { if (isIOS) {
const notificationsPriceAlertsEnabled = getFeatureFlag(FeatureFlags.NotificationPriceAlerts) const notificationsPriceAlertsEnabled = getFeatureFlag(FeatureFlags.NotificationPriceAlertsIOS)
const notificationsUnfundedWalletEnabled = getFeatureFlag(FeatureFlags.NotificationUnfundedWallets) const notificationsUnfundedWalletEnabled = getFeatureFlag(FeatureFlags.NotificationUnfundedWalletsIOS)
OneSignal.sendTags({ OneSignal.sendTags({
[OneSignalUserTagField.GatingPriceAlertsEnabled]: notificationsPriceAlertsEnabled ? 'true' : 'false', [OneSignalUserTagField.GatingPriceAlertsEnabled]: notificationsPriceAlertsEnabled ? 'true' : 'false',
......
import { PermissionStatus, scanFromURLAsync } from 'expo-barcode-scanner' import { BarCodeScanner, PermissionStatus } from 'expo-barcode-scanner'
import { BarCodeScanningResult, CameraType } from 'expo-camera' import { BarCodeScanningResult, CameraType } from 'expo-camera/build/Camera.types'
import { CameraProps, CameraView, useCameraPermissions } from 'expo-camera/next' import { CameraProps, CameraView, useCameraPermissions } from 'expo-camera/next'
import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
...@@ -59,7 +59,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -59,7 +59,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
const [infoLayout, setInfoLayout] = useState<LayoutRectangle | null>() const [infoLayout, setInfoLayout] = useState<LayoutRectangle | null>()
const [bottomLayout, setBottomLayout] = useState<LayoutRectangle | null>() const [bottomLayout, setBottomLayout] = useState<LayoutRectangle | null>()
const handleBarcodeScanned = useCallback( const handleBarCodeScanned = useCallback(
(result: BarCodeScanningResult): void => { (result: BarCodeScanningResult): void => {
if (shouldFreezeCamera) { if (shouldFreezeCamera) {
return return
...@@ -90,7 +90,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -90,7 +90,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
return return
} }
const result = (await scanFromURLAsync(uri, [BarcodeType.QR]))[0] const result = (await BarCodeScanner.scanFromURLAsync(uri, [BarCodeScanner.Constants.BarCodeType.qr]))[0]
if (!result) { if (!result) {
Alert.alert(t('qrScanner.error.none')) Alert.alert(t('qrScanner.error.none'))
...@@ -98,8 +98,8 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -98,8 +98,8 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
return return
} }
handleBarcodeScanned(result) handleBarCodeScanned(result)
}, [handleBarcodeScanned, isReadingImageFile, t]) }, [handleBarCodeScanned, isReadingImageFile, t])
useEffect(() => { useEffect(() => {
const handlePermissionStatus = async (): Promise<void> => { const handlePermissionStatus = async (): Promise<void> => {
...@@ -146,7 +146,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E ...@@ -146,7 +146,7 @@ export function QRCodeScanner(props: QRCodeScannerProps | WCScannerProps): JSX.E
}} }}
facing={CameraType.back} facing={CameraType.back}
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFillObject}
onBarcodeScanned={handleBarcodeScanned} onBarcodeScanned={handleBarCodeScanned}
/> />
)} )}
</Flex> </Flex>
......
...@@ -30,6 +30,10 @@ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseC ...@@ -30,6 +30,10 @@ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseC
const [showPopularInfo, setShowPopularInfo] = useState(false) const [showPopularInfo, setShowPopularInfo] = useState(false)
// Popular NFT collections data is only available on Mainnet
// TODO(WALL-5876): Update this once we have a way to fetch NFT collections for all chains
const showPopularNftCollections = !selectedChain || selectedChain === UniverseChainId.Mainnet
const onPressClearSearchHistory = (): void => { const onPressClearSearchHistory = (): void => {
dispatch(clearSearchHistory()) dispatch(clearSearchHistory())
} }
...@@ -79,10 +83,12 @@ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseC ...@@ -79,10 +83,12 @@ export function SearchEmptySection({ selectedChain }: { selectedChain: UniverseC
/> />
<SearchPopularTokens selectedChain={selectedChain} /> <SearchPopularTokens selectedChain={selectedChain} />
</Flex> </Flex>
<Flex gap="$spacing4"> {showPopularNftCollections && (
<SectionHeaderText icon={TrendUpIcon} title={t('explore.search.section.popularNFT')} /> <Flex gap="$spacing4">
<SearchPopularNFTCollections /> <SectionHeaderText icon={TrendUpIcon} title={t('explore.search.section.popularNFT')} />
</Flex> <SearchPopularNFTCollections />
</Flex>
)}
</AnimatedFlex> </AnimatedFlex>
<WarningModal <WarningModal
backgroundIconColor={colors.surface3.get()} backgroundIconColor={colors.surface3.get()}
......
...@@ -31,6 +31,7 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' ...@@ -31,6 +31,7 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { useExploreSearchQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useExploreSearchQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { toGraphQLChain } from 'uniswap/src/features/chains/utils'
import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchContext } from 'uniswap/src/features/search/SearchContext'
import { import {
NFTCollectionSearchResult, NFTCollectionSearchResult,
...@@ -61,7 +62,11 @@ export function SearchResultsSection({ ...@@ -61,7 +62,11 @@ export function SearchResultsSection({
error, error,
refetch, refetch,
} = useExploreSearchQuery({ } = useExploreSearchQuery({
variables: { searchQuery, nftCollectionsFilter: { nameQuery: searchQuery } }, variables: {
searchQuery,
nftCollectionsFilter: { nameQuery: searchQuery },
chains: selectedChain ? [toGraphQLChain(selectedChain)] : undefined,
},
}) })
const onRetry = useCallback(async () => { const onRetry = useCallback(async () => {
......
...@@ -3,7 +3,7 @@ import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native- ...@@ -3,7 +3,7 @@ import OneSignal, { NotificationReceivedEvent, OpenedEvent } from 'react-native-
import { NotificationType } from 'src/features/notifications/constants' import { NotificationType } from 'src/features/notifications/constants'
import { config } from 'uniswap/src/config' import { config } from 'uniswap/src/config'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { getFeatureFlagWithExposureLoggingDisabled } from 'uniswap/src/features/gating/hooks' import { getFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { GQL_QUERIES_TO_REFETCH_ON_TXN_UPDATE } from 'uniswap/src/features/portfolio/portfolioUpdates/constants' import { GQL_QUERIES_TO_REFETCH_ON_TXN_UPDATE } from 'uniswap/src/features/portfolio/portfolioUpdates/constants'
import { getUniqueId } from 'utilities/src/device/getUniqueId' import { getUniqueId } from 'utilities/src/device/getUniqueId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
...@@ -20,14 +20,14 @@ export const initOneSignal = (): void => { ...@@ -20,14 +20,14 @@ export const initOneSignal = (): void => {
const notificationType = additionalData?.notification_type const notificationType = additionalData?.notification_type
let enabled = false let enabled = false
// Some special notif filtering logic is needed for iOS, avoiding exposure // Some special notif filtering logic is needed for iOS
if (isIOS) { if (isIOS) {
switch (notificationType) { switch (notificationType) {
case NotificationType.UnfundedWalletReminder: case NotificationType.UnfundedWalletReminder:
enabled = getFeatureFlagWithExposureLoggingDisabled(FeatureFlags.NotificationUnfundedWallets) enabled = getFeatureFlag(FeatureFlags.NotificationPriceAlertsIOS)
break break
case NotificationType.PriceAlert: case NotificationType.PriceAlert:
enabled = getFeatureFlagWithExposureLoggingDisabled(FeatureFlags.NotificationPriceAlerts) enabled = getFeatureFlag(FeatureFlags.NotificationPriceAlertsIOS)
break break
default: default:
enabled = false enabled = false
......
...@@ -82,6 +82,7 @@ function preloadServiceProviderLogos(serviceProviders: FORServiceProvider[], isD ...@@ -82,6 +82,7 @@ function preloadServiceProviderLogos(serviceProviders: FORServiceProvider[], isD
} }
const PREDEFINED_AMOUNTS_SUPPORTED_CURRENCIES = ['usd', 'eur', 'gbp', 'aud', 'cad', 'sgd'] const PREDEFINED_AMOUNTS_SUPPORTED_CURRENCIES = ['usd', 'eur', 'gbp', 'aud', 'cad', 'sgd']
const US_STATES_WITH_RESTRICTIONS = 'US-NY'
export function FiatOnRampScreen({ navigation }: Props): JSX.Element { export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
const isOffRampEnabled = useFeatureFlag(FeatureFlags.FiatOffRamp) const isOffRampEnabled = useFeatureFlag(FeatureFlags.FiatOffRamp)
...@@ -398,7 +399,10 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element { ...@@ -398,7 +399,10 @@ export function FiatOnRampScreen({ navigation }: Props): JSX.Element {
meldSupportedFiatCurrency.code.toLowerCase(), meldSupportedFiatCurrency.code.toLowerCase(),
) )
const notAvailableInThisRegion = supportedFiatCurrencies?.length === 0 const notAvailableInThisRegion =
supportedFiatCurrencies?.length === 0 ||
(!supportedTokensLoading && supportedTokensList?.length === 0) ||
(US_STATES_WITH_RESTRICTIONS.includes(countryState || '') && quotes?.length === 0)
const { errorText } = useParseFiatOnRampError({ const { errorText } = useParseFiatOnRampError({
error: !notAvailableInThisRegion && quotesError, error: !notAvailableInThisRegion && quotesError,
......
...@@ -656,7 +656,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX. ...@@ -656,7 +656,7 @@ export function HomeScreen(props?: AppStackScreenProp<MobileScreens.Home>): JSX.
{contentHeader} {contentHeader}
</Animated.View> </Animated.View>
{isTabsDataLoaded && ( {isTabsDataLoaded && isLayoutReady && (
<TraceTabView <TraceTabView
lazy lazy
initialLayout={{ initialLayout={{
......
import { useEffect } from 'react' import { useEffect, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { useNftsTabQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useNftsTabQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
...@@ -25,6 +25,14 @@ export function useHomeScreenState(): { ...@@ -25,6 +25,14 @@ export function useHomeScreenState(): {
) )
const { gqlChains } = useEnabledChains() const { gqlChains } = useEnabledChains()
const neverCached = hasUsedWalletFromCache === undefined
// There's a race condition during onboarding where the editAccountSaga is
// editing the account in redux in an unconventional way that causes the
// hasUsedWalletFromCache to reset after being set by this hook. This ref
// is a work around to only trigger the loading state once.
const dataLoadedRef = useRef(false)
const { data: balancesById, loading: areBalancesLoading } = usePortfolioBalances({ const { data: balancesById, loading: areBalancesLoading } = usePortfolioBalances({
address, address,
skip: hasUsedWalletFromCache, skip: hasUsedWalletFromCache,
...@@ -48,24 +56,27 @@ export function useHomeScreenState(): { ...@@ -48,24 +56,27 @@ export function useHomeScreenState(): {
}) })
const hasNft = !!nftData?.nftBalances?.edges.length const hasNft = !!nftData?.nftBalances?.edges.length
const hasTokenBalance = !!Object.entries(balancesById || {}).length const hasTokenBalance = balancesById ? Object.keys(balancesById).length > 0 : false
const hasUsedWalletFromRemote = hasTokenBalance || hasNft || hasActivity const hasUsedWalletFromRemote = hasTokenBalance || hasNft || hasActivity
const dataIsLoading = areBalancesLoading || areNFTsLoading || isActivityLoading const dataIsLoading = areBalancesLoading || areNFTsLoading || isActivityLoading
// Note: This is to prevent loading the empty wallet state for an active const isTabsDataLoaded = neverCached ? !dataIsLoading : true
// wallet loading tabs for the first time.
const isTabsDataLoaded = !(dataIsLoading && hasUsedWalletFromCache)
const hasUsedWallet = hasUsedWalletFromCache || hasUsedWalletFromRemote const hasUsedWallet = hasUsedWalletFromCache || hasUsedWalletFromRemote
const shouldUpdateCache = neverCached && isTabsDataLoaded
const addressIsNowUsed = hasUsedWalletFromCache === false && hasUsedWalletFromRemote
useEffect(() => { useEffect(() => {
if (hasUsedWallet && !hasUsedWalletFromCache) { if (shouldUpdateCache || addressIsNowUsed) {
dispatch(setHasBalanceOrActivity({ address, hasBalanceOrActivity: true })) dispatch(setHasBalanceOrActivity({ address, hasBalanceOrActivity: hasUsedWallet }))
} }
}, [hasUsedWallet, dispatch, address, hasUsedWalletFromCache]) }, [hasUsedWallet, dispatch, address, shouldUpdateCache, addressIsNowUsed])
if (!dataLoadedRef.current && isTabsDataLoaded) {
dataLoadedRef.current = true
}
return { return {
showEmptyWalletState: !hasUsedWallet, showEmptyWalletState: !hasUsedWallet,
isTabsDataLoaded, isTabsDataLoaded: dataLoadedRef.current || isTabsDataLoaded,
} }
} }
import React, { memo } from 'react' import React, { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { BackHeader } from 'src/components/layout/BackHeader' import { BackHeader } from 'src/components/layout/BackHeader'
import { Screen } from 'src/components/layout/Screen' import { Screen } from 'src/components/layout/Screen'
...@@ -14,6 +14,8 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' ...@@ -14,6 +14,8 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants' import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { NotificationToggleLoggingType } from 'uniswap/src/features/telemetry/types'
import { isIOS } from 'utilities/src/platform'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { useAccountsList } from 'wallet/src/features/wallet/hooks' import { useAccountsList } from 'wallet/src/features/wallet/hooks'
...@@ -22,13 +24,26 @@ export function SettingsNotificationsScreen(): JSX.Element { ...@@ -22,13 +24,26 @@ export function SettingsNotificationsScreen(): JSX.Element {
const accounts = useAccountsList() const accounts = useAccountsList()
const priceAlertsToggleEnabled = useFeatureFlag(FeatureFlags.NotificationPriceAlerts) const priceAlertsToggleEnabled = useFeatureFlag(
isIOS ? FeatureFlags.NotificationPriceAlertsIOS : FeatureFlags.NotificationPriceAlertsAndroid,
)
const onGeneralUpdatesToggle = useCallback(
(enabled: boolean) => onPermissionChanged(enabled, NotifSettingType.GeneralUpdates),
[],
)
const onPriceAlertsToggle = useCallback(
(enabled: boolean) => onPermissionChanged(enabled, NotifSettingType.PriceAlerts),
[],
)
const { isEnabled: updatesNotifEnabled, toggle: toggleUpdatesNotif } = useSettingNotificationToggle({ const { isEnabled: updatesNotifEnabled, toggle: toggleUpdatesNotif } = useSettingNotificationToggle({
type: NotifSettingType.GeneralUpdates, type: NotifSettingType.GeneralUpdates,
onToggle: onGeneralUpdatesToggle,
}) })
const { isEnabled: priceAlertsNotifEnabled, toggle: togglePriceAlertsNotif } = useSettingNotificationToggle({ const { isEnabled: priceAlertsNotifEnabled, toggle: togglePriceAlertsNotif } = useSettingNotificationToggle({
type: NotifSettingType.PriceAlerts, type: NotifSettingType.PriceAlerts,
onToggle: onPriceAlertsToggle,
}) })
return ( return (
...@@ -117,14 +132,14 @@ function NotificationSettingRow({ ...@@ -117,14 +132,14 @@ function NotificationSettingRow({
) )
} }
function onPermissionChanged(enabled: boolean): void { function onPermissionChanged(enabled: boolean, type: NotificationToggleLoggingType): void {
sendAnalyticsEvent(MobileEventName.NotificationsToggled, { enabled }) sendAnalyticsEvent(MobileEventName.NotificationsToggled, { enabled, type })
} }
function _AddressNotificationsSwitch({ address }: { address: string }): JSX.Element { function _AddressNotificationsSwitch({ address }: { address: string }): JSX.Element {
const { isEnabled, isPending, toggle } = useAddressNotificationToggle({ const { isEnabled, isPending, toggle } = useAddressNotificationToggle({
address, address,
onToggle: onPermissionChanged, onToggle: (enabled) => onPermissionChanged(enabled, 'wallet_activity'),
}) })
return <Switch checked={isEnabled} disabled={isPending} variant="branded" onCheckedChange={toggle} /> return <Switch checked={isEnabled} disabled={isPending} variant="branded" onCheckedChange={toggle} />
......
...@@ -126,4 +126,16 @@ ...@@ -126,4 +126,16 @@
<changefreq>weekly</changefreq> <changefreq>weekly</changefreq>
<priority>0.5</priority> <priority>0.5</priority>
</url> </url>
<url>
<loc>https://app.uniswap.org/positions/create</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://app.uniswap.org/positions</loc>
<lastmod>2024-09-17T19:57:27.976Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
</urlset> </urlset>
...@@ -100,7 +100,6 @@ ...@@ -100,7 +100,6 @@
"https://x6ahx1oagk.execute-api.us-east-2.amazonaws.com", "https://x6ahx1oagk.execute-api.us-east-2.amazonaws.com",
"https://mainnet.era.zksync.io/", "https://mainnet.era.zksync.io/",
"https://8mr3mthjba.execute-api.us-east-2.amazonaws.com", "https://8mr3mthjba.execute-api.us-east-2.amazonaws.com",
"https://mainnet.unichain.org/",
"wss://*.uniswap.org", "wss://*.uniswap.org",
"wss://relay.walletconnect.com", "wss://relay.walletconnect.com",
"wss://relay.walletconnect.org", "wss://relay.walletconnect.org",
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -92,7 +92,7 @@ export function LiquidityRangeInput({ ...@@ -92,7 +92,7 @@ export function LiquidityRangeInput({
) )
// Set via a callback from the LiquidityPositionRangeChart, which is important when the price axis is auto-scaled. // Set via a callback from the LiquidityPositionRangeChart, which is important when the price axis is auto-scaled.
// This is also used to set the bounds of the ActiveLiquidityChart, so it's necessary to keep separate from the zooming state. // This is also used to set the bounds of the ActiveLiquiditChart, so it's necessary to keep separate from the zooming state.
const [boundaryPrices, setBoundaryPrices] = useState<[number, number]>() const [boundaryPrices, setBoundaryPrices] = useState<[number, number]>()
const [zoomFactor, setZoomFactor] = useState(1) const [zoomFactor, setZoomFactor] = useState(1)
...@@ -278,7 +278,6 @@ export function LiquidityRangeInput({ ...@@ -278,7 +278,6 @@ export function LiquidityRangeInput({
width={showChartErrorView ? sizes.chartContainerWidth : sizes.loadedPriceChartWidth} width={showChartErrorView ? sizes.chartContainerWidth : sizes.loadedPriceChartWidth}
height={sizes.chartHeight + sizes.bottomAxisHeight} height={sizes.chartHeight + sizes.bottomAxisHeight}
overflow="hidden" overflow="hidden"
zIndex={1}
> >
{(priceData.loading || showChartErrorView) && (!priceData.entries || priceData.entries.length === 0) && ( {(priceData.loading || showChartErrorView) && (!priceData.entries || priceData.entries.length === 0) && (
<Shine height={sizes.chartHeight} disabled={showChartErrorView} zIndex={0}> <Shine height={sizes.chartHeight} disabled={showChartErrorView} zIndex={0}>
...@@ -303,7 +302,6 @@ export function LiquidityRangeInput({ ...@@ -303,7 +302,6 @@ export function LiquidityRangeInput({
right={0} right={0}
top={0} top={0}
pointerEvents="none" pointerEvents="none"
zIndex={2}
> >
{(liquidityDataLoading || priceData.loading) && ( {(liquidityDataLoading || priceData.loading) && (
<Shine <Shine
...@@ -347,9 +345,13 @@ export function LiquidityRangeInput({ ...@@ -347,9 +345,13 @@ export function LiquidityRangeInput({
// While scrolling we receive updates to the range because the yScale changes, // While scrolling we receive updates to the range because the yScale changes,
// but we can filter them out because they have an undefined "mode". // but we can filter them out because they have an undefined "mode".
// The initial range suggestion also comes with an undefined "mode", so we allow that here. // The initial range suggestion also comes with an undefined "mode", so we allow that here.
const rejectAutoRangeSuggestion = const hasValidRange =
minPrice !== undefined && maxPrice !== undefined && minPrice >= 0 && maxPrice >= 0 minPrice !== undefined &&
if (!mode && rejectAutoRangeSuggestion) { maxPrice !== undefined &&
minPrice < maxPrice &&
minPrice >= 0 &&
maxPrice >= 0
if (!mode && hasValidRange) {
return return
} }
setMinPrice(domain[0]) setMinPrice(domain[0])
......
...@@ -65,7 +65,7 @@ export function DepositInputForm({ ...@@ -65,7 +65,7 @@ export function DepositInputForm({
const handleOnSetMax = (field: PositionField) => { const handleOnSetMax = (field: PositionField) => {
return (amount: string) => { return (amount: string) => {
setFocusedInputField(field) setFocusedInputField(field)
onSetMax(field, amount) onSetMax(field, amount) // TODO(WEB-4978): update this to account for gas
} }
} }
......
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb'
import { import {
LiquidityPositionRangeChart, LiquidityPositionRangeChart,
LiquidityPositionRangeChartLoader, LiquidityPositionRangeChartLoader,
...@@ -74,146 +74,6 @@ export function LiquidityPositionCardLoader() { ...@@ -74,146 +74,6 @@ export function LiquidityPositionCardLoader() {
) )
} }
function useDropdownOptions(
liquidityPosition: PositionInfo,
showVisibilityOption?: boolean,
isVisible?: boolean,
): MenuOptionItem[] {
const { t } = useTranslation()
const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data)
const isMigrateToV4Enabled = useFeatureFlag(FeatureFlags.MigrateV3ToV4)
const isOpenLiquidityPosition = liquidityPosition.status !== PositionStatus.CLOSED
const dispatch = useAppDispatch()
const navigate = useNavigate()
const account = useAccount()
const switchChain = useSwitchChain()
return useMemo(() => {
const chainInfo = getChainInfo(liquidityPosition.chainId)
const addLiquidityOption = {
onPress: () => {
dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: liquidityPosition }))
},
label: t('common.addLiquidity'),
Icon: Plus,
}
const removeLiquidityOption: MenuOptionItem | undefined = isOpenLiquidityPosition
? {
onPress: () => {
dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: liquidityPosition }))
},
label: t('pool.removeLiquidity'),
Icon: Minus,
}
: undefined
const poolInfoOption = {
onPress: () => {
if (!liquidityPosition.poolId) {
return
}
navigate(getPoolDetailsURL(liquidityPosition.poolId, toGraphQLChain(liquidityPosition.chainId)))
},
label: t('pool.info'),
Icon: InfoCircleFilled,
}
const hideOption: MenuOptionItem | undefined = showVisibilityOption
? {
onPress: () => {
dispatch(
togglePositionVisibility({
poolId: liquidityPosition.poolId,
tokenId: liquidityPosition.tokenId,
chainId: liquidityPosition.chainId,
}),
)
},
label: isVisible ? t('common.hide.button') : t('common.unhide'),
Icon: isVisible ? EyeOff : Eye,
showDivider: true,
}
: undefined
if (liquidityPosition.version === ProtocolVersion.V2) {
const migrateV2Option = isOpenLiquidityPosition
? {
onPress: async () => {
if (chainInfo.id !== account.chainId) {
await switchChain(chainInfo.id)
}
navigate(`/migrate/v2/${liquidityPosition.liquidityToken?.address ?? ''}`)
},
label: t('pool.migrateLiquidity'),
Icon: RightArrow,
}
: undefined
return [
isOpenLiquidityPosition ? addLiquidityOption : undefined, // closed v2 positions cannot re-add liquidity since the erc20 liquidity token is permanently burned when closed. whereas v3 positions can be re-opened
removeLiquidityOption,
migrateV2Option,
poolInfoOption,
hideOption,
].filter((o): o is MenuOptionItem => o !== undefined)
}
const collectFeesOption: MenuOptionItem | undefined = isOpenLiquidityPosition
? {
onPress: () => {
dispatch(
setOpenModal({
name: ModalName.ClaimFee,
initialState: { ...liquidityPosition, collectAsWeth: false },
}),
)
},
label: t('pool.collectFees'),
Icon: Dollar,
}
: undefined
const migrateV3Option: MenuOptionItem | undefined =
isOpenLiquidityPosition &&
isV4DataEnabled &&
isMigrateToV4Enabled &&
!isV4UnsupportedChain(liquidityPosition.chainId)
? {
onPress: () => {
navigate(`/migrate/v3/${chainInfo.urlParam}/${liquidityPosition.tokenId}`)
},
label: t('pool.migrateLiquidity'),
Icon: RightArrow,
}
: undefined
return [
collectFeesOption,
addLiquidityOption,
removeLiquidityOption,
migrateV3Option,
poolInfoOption,
hideOption,
].filter((o): o is MenuOptionItem => o !== undefined)
}, [
account.chainId,
dispatch,
isMigrateToV4Enabled,
isOpenLiquidityPosition,
isV4DataEnabled,
isVisible,
liquidityPosition,
navigate,
showVisibilityOption,
switchChain,
t,
])
}
export function LiquidityPositionCard({ export function LiquidityPositionCard({
liquidityPosition, liquidityPosition,
isMiniVersion, isMiniVersion,
...@@ -228,9 +88,16 @@ export function LiquidityPositionCard({ ...@@ -228,9 +88,16 @@ export function LiquidityPositionCard({
isVisible?: boolean isVisible?: boolean
}) { }) {
const { formatCurrencyAmount } = useLocalizationContext() const { formatCurrencyAmount } = useLocalizationContext()
const { t } = useTranslation()
const isTouchDevice = useIsTouchDevice() const isTouchDevice = useIsTouchDevice()
const isV4Enabled = useFeatureFlag(FeatureFlags.V4Data)
const isMigrateEnabled = useFeatureFlag(FeatureFlags.MigrateV3ToV4)
const [pricesInverted, setPricesInverted] = useState(false) const [pricesInverted, setPricesInverted] = useState(false)
const dispatch = useAppDispatch()
const navigate = useNavigate()
const account = useAccount()
const switchChain = useSwitchChain()
const { fiatFeeValue0, fiatFeeValue1, fiatValue0, fiatValue1, priceOrdering, apr } = const { fiatFeeValue0, fiatFeeValue1, fiatValue0, fiatValue1, priceOrdering, apr } =
useV3OrV4PositionDerivedInfo(liquidityPosition) useV3OrV4PositionDerivedInfo(liquidityPosition)
...@@ -257,7 +124,112 @@ export function LiquidityPositionCard({ ...@@ -257,7 +124,112 @@ export function LiquidityPositionCard({
}) })
: undefined : undefined
const dropdownOptions = useDropdownOptions(liquidityPosition, showVisibilityOption, isVisible) const dropdownOptions = useMemo(() => {
const hideOption = [
{
key: 'position-card-hide-liquidity',
onPress: () => {
dispatch(
togglePositionVisibility({
poolId: liquidityPosition.poolId,
tokenId: liquidityPosition.tokenId,
chainId: liquidityPosition.chainId,
}),
)
},
label: isVisible ? t('common.hide.button') : t('common.unhide'),
Icon: isVisible ? EyeOff : Eye,
showDivider: true,
},
]
const v2Options = [
{
key: 'position-card-add-liquidity',
onPress: () => {
dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: liquidityPosition }))
},
label: t('common.addLiquidity'),
Icon: Plus,
},
{
key: 'position-card-remove-liquidity',
onPress: () => {
dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: liquidityPosition }))
},
label: t('pool.removeLiquidity'),
Icon: Minus,
},
]
const chainInfo = getChainInfo(liquidityPosition.chainId)
const migrateV2Option = {
key: 'position-card-migrate',
onPress: async () => {
if (chainInfo.id !== account.chainId) {
await switchChain(chainInfo.id)
}
navigate(`/migrate/v2/${liquidityPosition.liquidityToken?.address ?? ''}`)
},
label: t('pool.migrateLiquidity'),
Icon: RightArrow,
}
if (liquidityPosition.version === ProtocolVersion.V2) {
return [...v2Options, migrateV2Option, ...(showVisibilityOption ? hideOption : [])]
}
const migrateV3Option = isV4UnsupportedChain(liquidityPosition.chainId)
? undefined
: {
key: 'position-card-migrate',
onPress: () => {
navigate(`/migrate/v3/${chainInfo.urlParam}/${liquidityPosition.tokenId}`)
},
label: t('pool.migrateLiquidity'),
Icon: RightArrow,
}
return [
{
key: 'position-card-collect-fees',
onPress: () => {
dispatch(
setOpenModal({ name: ModalName.ClaimFee, initialState: { ...liquidityPosition, collectAsWeth: false } }),
)
},
label: t('pool.collectFees'),
Icon: Dollar,
},
...v2Options,
...(isV4Enabled && isMigrateEnabled && migrateV3Option ? [migrateV3Option] : []),
{
key: 'position-card-pool-info',
onPress: () => {
if (!liquidityPosition.poolId) {
return
}
navigate(getPoolDetailsURL(liquidityPosition.poolId, toGraphQLChain(liquidityPosition.chainId)))
},
label: t('pool.info'),
Icon: InfoCircleFilled,
},
...(showVisibilityOption ? hideOption : []),
]
}, [
showVisibilityOption,
liquidityPosition,
isMigrateEnabled,
isV4Enabled,
dispatch,
isVisible,
t,
account.chainId,
navigate,
switchChain,
])
const priceOrderingForChart = useMemo(() => { const priceOrderingForChart = useMemo(() => {
if ( if (
...@@ -440,10 +412,9 @@ function MiniPositionCard({ ...@@ -440,10 +412,9 @@ function MiniPositionCard({
const PositionPopoverMoreMenu = ({ menuOptions }: { menuOptions: MenuOptionItem[] }) => { const PositionPopoverMoreMenu = ({ menuOptions }: { menuOptions: MenuOptionItem[] }) => {
const colors = useSporeColors() const colors = useSporeColors()
const [isOpen, setIsOpen] = useState(false)
return ( return (
<Popover open={isOpen} placement="bottom-end" allowFlip offset={{ crossAxis: 6 }}> <Popover placement="bottom-end" allowFlip offset={{ crossAxis: 6 }}>
<TouchableArea <TouchableArea
position="absolute" position="absolute"
top="$spacing16" top="$spacing16"
...@@ -455,7 +426,6 @@ const PositionPopoverMoreMenu = ({ menuOptions }: { menuOptions: MenuOptionItem[ ...@@ -455,7 +426,6 @@ const PositionPopoverMoreMenu = ({ menuOptions }: { menuOptions: MenuOptionItem[
onPress={(event) => { onPress={(event) => {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
setIsOpen(true)
}} }}
> >
<Popover.Trigger p="$spacing8"> <Popover.Trigger p="$spacing8">
...@@ -471,7 +441,7 @@ const PositionPopoverMoreMenu = ({ menuOptions }: { menuOptions: MenuOptionItem[ ...@@ -471,7 +441,7 @@ const PositionPopoverMoreMenu = ({ menuOptions }: { menuOptions: MenuOptionItem[
}} }}
backgroundColor="transparent" backgroundColor="transparent"
> >
<MenuContent items={menuOptions} onItemClick={() => setIsOpen(false)} /> <MenuContent items={menuOptions} />
</Popover.Content> </Popover.Content>
</Popover> </Popover>
) )
......
...@@ -93,18 +93,14 @@ export function useAllFeeTierPoolData({ ...@@ -93,18 +93,14 @@ export function useAllFeeTierPoolData({
feeTierData: mergeFeeTiers( feeTierData: mergeFeeTiers(
feeTierData, feeTierData,
Object.values( Object.values(
getDefaultFeeTiersForChainWithDynamicFeeTier({ getDefaultFeeTiersForChainWithDynamicFeeTier({ chainId, dynamicFeeTierEnabled: withDynamicFeeTier }),
chainId,
dynamicFeeTierEnabled: withDynamicFeeTier,
protocolVersion,
}),
), ),
formatPercent, formatPercent,
t('fee.dynamic'), t('fee.dynamic'),
), ),
hasExistingFeeTiers: Object.values(feeTierData).length > 0, hasExistingFeeTiers: Object.values(feeTierData).length > 0,
} }
}, [poolData, sortedCurrencies, chainId, withDynamicFeeTier, formatPercent, protocolVersion, t]) }, [poolData, sortedCurrencies, chainId, withDynamicFeeTier, formatPercent, t])
} }
/** /**
......
...@@ -491,17 +491,10 @@ export function mergeFeeTiers( ...@@ -491,17 +491,10 @@ export function mergeFeeTiers(
} }
function getDefaultFeeTiersForChain( function getDefaultFeeTiersForChain(
chainId: UniverseChainId | undefined, chainId?: UniverseChainId,
protocolVersion: ProtocolVersion,
): Record<FeeAmount, { feeAmount: FeeAmount; tickSpacing: number }> { ): Record<FeeAmount, { feeAmount: FeeAmount; tickSpacing: number }> {
const feeData = Object.values(defaultFeeTiers) const feeData = Object.values(defaultFeeTiers)
.filter((feeTier) => { .filter((feeTier) => !feeTier.supportedChainIds || (chainId && feeTier.supportedChainIds.includes(chainId)))
// Only filter by chain support if we're on V3
if (protocolVersion === ProtocolVersion.V3) {
return !feeTier.supportedChainIds || (chainId && feeTier.supportedChainIds.includes(chainId))
}
return !feeTier.supportedChainIds
})
.map((feeTier) => feeTier.feeData) .map((feeTier) => feeTier.feeData)
return feeData.reduce( return feeData.reduce(
...@@ -516,32 +509,27 @@ function getDefaultFeeTiersForChain( ...@@ -516,32 +509,27 @@ function getDefaultFeeTiersForChain(
export function getDefaultFeeTiersForChainWithDynamicFeeTier({ export function getDefaultFeeTiersForChainWithDynamicFeeTier({
chainId, chainId,
dynamicFeeTierEnabled, dynamicFeeTierEnabled,
protocolVersion,
}: { }: {
chainId?: UniverseChainId chainId?: UniverseChainId
dynamicFeeTierEnabled: boolean dynamicFeeTierEnabled: boolean
protocolVersion: ProtocolVersion
}) { }) {
const feeTiers = getDefaultFeeTiersForChain(chainId, protocolVersion)
if (!dynamicFeeTierEnabled) { if (!dynamicFeeTierEnabled) {
return feeTiers return getDefaultFeeTiersForChain(chainId)
} }
return { ...feeTiers, [DYNAMIC_FEE_DATA.feeAmount]: DYNAMIC_FEE_DATA } return { ...getDefaultFeeTiersForChain(chainId), [DYNAMIC_FEE_DATA.feeAmount]: DYNAMIC_FEE_DATA }
} }
export function getDefaultFeeTiersWithData({ export function getDefaultFeeTiersWithData({
chainId, chainId,
feeTierData, feeTierData,
protocolVersion,
t, t,
}: { }: {
chainId?: UniverseChainId chainId?: UniverseChainId
feeTierData: Record<number, FeeTierData> feeTierData: Record<number, FeeTierData>
protocolVersion: ProtocolVersion
t: AppTFunction t: AppTFunction
}) { }) {
const defaultFeeTiersForChain = getDefaultFeeTiersForChain(chainId, protocolVersion) const defaultFeeTiersForChain = getDefaultFeeTiersForChain(chainId)
const feeTiers = [ const feeTiers = [
{ {
...@@ -595,9 +583,7 @@ export function getDefaultFeeTiersWithData({ ...@@ -595,9 +583,7 @@ export function getDefaultFeeTiersWithData({
}, },
] as const ] as const
return feeTiers.filter( return feeTiers.filter((feeTier) => Object.keys(feeTierData).includes(feeTier.tier.toString()))
(feeTier) => feeTier.value !== undefined && Object.keys(feeTierData).includes(feeTier.tier.toString()),
)
} }
export function isDynamicFeeTier(feeData: FeeData): feeData is DynamicFeeData { export function isDynamicFeeTier(feeData: FeeData): feeData is DynamicFeeData {
......
...@@ -54,9 +54,9 @@ const PoolTransactionColumnWidth: { [key in PoolTransactionColumn]: number } = { ...@@ -54,9 +54,9 @@ const PoolTransactionColumnWidth: { [key in PoolTransactionColumn]: number } = {
function comparePoolTokens(tokenA: PoolTableTransaction['pool']['token0'], tokenB?: Token) { function comparePoolTokens(tokenA: PoolTableTransaction['pool']['token0'], tokenB?: Token) {
if (tokenB?.address === NATIVE_CHAIN_ID) { if (tokenB?.address === NATIVE_CHAIN_ID) {
const chainId = supportedChainIdFromGQLChain(tokenB.chain) const chainId = supportedChainIdFromGQLChain(tokenB.chain)
return chainId && tokenA.id?.toLowerCase() === WRAPPED_NATIVE_CURRENCY[chainId]?.address.toLowerCase() return chainId && tokenA.id.toLowerCase() === WRAPPED_NATIVE_CURRENCY[chainId]?.address.toLowerCase()
} }
return tokenA.id?.toLowerCase() === tokenB?.address?.toLowerCase() return tokenA.id.toLowerCase() === tokenB?.address?.toLowerCase()
} }
export function PoolDetailsTransactionsTable({ export function PoolDetailsTransactionsTable({
......
...@@ -7,7 +7,7 @@ import { useNavigate } from 'react-router-dom' ...@@ -7,7 +7,7 @@ import { useNavigate } from 'react-router-dom'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { useMultichainContext } from 'state/multichain/useMultichainContext' import { useMultichainContext } from 'state/multichain/useMultichainContext'
import { serializeSwapStateToURLParameters } from 'state/swap/hooks' import { serializeSwapStateToURLParameters } from 'state/swap/hooks'
import { ClickableTamaguiStyle, ExternalLink } from 'theme/components' import { ClickableTamaguiStyle, ExternalLink, HideSmall } from 'theme/components'
import { useIsDarkMode } from 'theme/components/ThemeToggle' import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { ElementAfterText, Flex, Text, TouchableArea, TouchableAreaEvent, useSporeColors } from 'ui/src' import { ElementAfterText, Flex, Text, TouchableArea, TouchableAreaEvent, useSporeColors } from 'ui/src'
import { UNICHAIN_BANNER_COLD, UNICHAIN_BANNER_WARM } from 'ui/src/assets' import { UNICHAIN_BANNER_COLD, UNICHAIN_BANNER_WARM } from 'ui/src/assets'
...@@ -269,16 +269,15 @@ function CardInner({ ...@@ -269,16 +269,15 @@ function CardInner({
textProps={{ color: textColor ?? '$neutral1', variant: 'subheading2' }} textProps={{ color: textColor ?? '$neutral1', variant: 'subheading2' }}
element={isNew ? <NewTag /> : undefined} element={isNew ? <NewTag /> : undefined}
/> />
<Flex $md={{ display: 'none' }}> <HideSmall>
<Text variant="body4" color={textColor ?? '$neutral2'}> <Text variant="body4" color={textColor ?? '$neutral2'}>
{subtitle} {subtitle}
</Text> </Text>
</Flex> </HideSmall>
</Flex> </Flex>
{onDismiss ? ( {onDismiss ? (
<TouchableArea <TouchableArea
alignSelf="flex-start" alignSelf="flex-start"
$md={{ alignSelf: 'center' }}
hitSlop={ICON_SIZE} hitSlop={ICON_SIZE}
onPress={(e: TouchableAreaEvent) => { onPress={(e: TouchableAreaEvent) => {
e.stopPropagation() e.stopPropagation()
...@@ -288,9 +287,7 @@ function CardInner({ ...@@ -288,9 +287,7 @@ function CardInner({
<X color="$neutral3" size={ICON_SIZE} /> <X color="$neutral3" size={ICON_SIZE} />
</TouchableArea> </TouchableArea>
) : ( ) : (
<TouchableArea alignSelf="flex-start" $md={{ alignSelf: 'center' }}> <ArrowUpRight width={ICON_SIZE_PX} height={ICON_SIZE_PX} color={textColor} />
<ArrowUpRight width={ICON_SIZE_PX} height={ICON_SIZE_PX} color={textColor} />
</TouchableArea>
)} )}
</Flex> </Flex>
</Flex> </Flex>
......
...@@ -126,7 +126,6 @@ export const TokenDetailsHeader = () => { ...@@ -126,7 +126,6 @@ export const TokenDetailsHeader = () => {
$sm={{ mb: '$spacing8', alignItems: 'flex-start' }} $sm={{ mb: '$spacing8', alignItems: 'flex-start' }}
animation="quick" animation="quick"
data-testid="token-info-container" data-testid="token-info-container"
zIndex="$default"
> >
<Flex row alignItems="center" $sm={{ alignItems: 'flex-start', flexDirection: 'column' }} gap="$gap12"> <Flex row alignItems="center" $sm={{ alignItems: 'flex-start', flexDirection: 'column' }} gap="$gap12">
<PortfolioLogo currencies={[currency]} chainId={currency.chainId} size={32} /> <PortfolioLogo currencies={[currency]} chainId={currency.chainId} size={32} />
......
...@@ -86,7 +86,7 @@ export default function TableNetworkFilter({ showMultichainOption = true }: { sh ...@@ -86,7 +86,7 @@ export default function TableNetworkFilter({ showMultichainOption = true }: { sh
toggleOpen={toggleMenu} toggleOpen={toggleMenu}
menuLabel={ menuLabel={
<NetworkLabel> <NetworkLabel>
{(!currentChainId || !isSupportedChainCallback(currentChainId)) && showMultichainOption ? ( {!currentChainId && showMultichainOption ? (
<NetworkLogo chainId={null} /> <NetworkLogo chainId={null} />
) : ( ) : (
<ChainLogo <ChainLogo
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Token, Token,
useSearchTokensWebQuery, useSearchTokensWebQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { isBackendSupportedChain } from 'uniswap/src/features/chains/utils' import { isBackendSupportedChain } from 'uniswap/src/features/chains/utils'
// Filters out results that are undefined, or where the token's chain is not supported in explore. // Filters out results that are undefined, or where the token's chain is not supported in explore.
...@@ -13,12 +12,7 @@ function isExploreSupportedToken(token: GqlSearchToken | undefined): token is To ...@@ -13,12 +12,7 @@ function isExploreSupportedToken(token: GqlSearchToken | undefined): token is To
} }
export function useSearchTokens(searchQuery: string = '') { export function useSearchTokens(searchQuery: string = '') {
const { gqlChains: chains } = useEnabledChains() const { data, loading, error } = useSearchTokensWebQuery({ variables: { searchQuery }, skip: searchQuery === '' })
const { data, loading, error } = useSearchTokensWebQuery({
variables: { searchQuery, chains },
skip: searchQuery === '',
})
return useMemo(() => { return useMemo(() => {
const sortedTokens = data?.searchTokens?.filter(isExploreSupportedToken) ?? [] const sortedTokens = data?.searchTokens?.filter(isExploreSupportedToken) ?? []
......
...@@ -126,10 +126,10 @@ export function usePoolData( ...@@ -126,10 +126,10 @@ export function usePoolData(
protocolVersion: pool.protocolVersion, protocolVersion: pool.protocolVersion,
token0: pool.token0 as Token, token0: pool.token0 as Token,
tvlToken0: pool.token0Supply, tvlToken0: pool.token0Supply,
token0Price: pool.token0?.project?.markets?.[0]?.price?.value ?? pool.token0?.market?.price?.value, token0Price: pool.token0?.project?.markets?.[0]?.price?.value,
token1: pool.token1 as Token, token1: pool.token1 as Token,
tvlToken1: pool.token1Supply, tvlToken1: pool.token1Supply,
token1Price: pool.token1?.project?.markets?.[0]?.price?.value ?? pool.token1?.market?.price?.value, token1Price: pool.token1?.project?.markets?.[0]?.price?.value,
feeTier, feeTier,
volumeUSD24H: pool.volume24h?.value, volumeUSD24H: pool.volume24h?.value,
volumeUSD24HChange: calc24HVolChange(pool.historicalVolume?.concat()), volumeUSD24HChange: calc24HVolChange(pool.historicalVolume?.concat()),
......
...@@ -30,11 +30,11 @@ export interface PoolTableTransaction { ...@@ -30,11 +30,11 @@ export interface PoolTableTransaction {
transaction: string transaction: string
pool: { pool: {
token0: { token0: {
id: string | null id: string
symbol: string symbol: string
} }
token1: { token1: {
id: string | null id: string
symbol: string symbol: string
} }
} }
...@@ -189,11 +189,11 @@ export function usePoolTransactions( ...@@ -189,11 +189,11 @@ export function usePoolTransactions(
transaction: tx.hash, transaction: tx.hash,
pool: { pool: {
token0: { token0: {
id: tx.token0.address ?? null, id: tx.token0.address ?? '',
symbol: tx.token0.symbol ?? '', symbol: tx.token0.symbol ?? '',
}, },
token1: { token1: {
id: tx.token1.address ?? null, id: tx.token1.address ?? '',
symbol: tx.token1.symbol ?? '', symbol: tx.token1.symbol ?? '',
}, },
}, },
......
...@@ -2,11 +2,10 @@ import useIsWindowVisible from 'hooks/useIsWindowVisible' ...@@ -2,11 +2,10 @@ import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { useCallback, useMemo, useRef } from 'react' import { useCallback, useMemo, useRef } from 'react'
import { import {
Chain, Chain,
PoolTransaction,
PoolTransactionType, PoolTransactionType,
PoolTxFragment,
useV2TransactionsQuery, useV2TransactionsQuery,
useV3TransactionsQuery, useV3TransactionsQuery,
useV4TransactionsQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
export enum TransactionType { export enum TransactionType {
...@@ -29,15 +28,6 @@ export function useAllTransactions( ...@@ -29,15 +28,6 @@ export function useAllTransactions(
) { ) {
const isWindowVisible = useIsWindowVisible() const isWindowVisible = useIsWindowVisible()
const {
data: dataV4,
loading: loadingV4,
error: errorV4,
fetchMore: fetchMoreV4,
} = useV4TransactionsQuery({
variables: { chain, first: ALL_TX_DEFAULT_QUERY_SIZE },
skip: !isWindowVisible,
})
const { const {
data: dataV3, data: dataV3,
loading: loadingV3, loading: loadingV3,
...@@ -57,39 +47,17 @@ export function useAllTransactions( ...@@ -57,39 +47,17 @@ export function useAllTransactions(
skip: !isWindowVisible, skip: !isWindowVisible,
}) })
const loadingMoreV4 = useRef(false)
const loadingMoreV3 = useRef(false) const loadingMoreV3 = useRef(false)
const loadingMoreV2 = useRef(false) const loadingMoreV2 = useRef(false)
const querySizeRef = useRef(ALL_TX_DEFAULT_QUERY_SIZE) const querySizeRef = useRef(ALL_TX_DEFAULT_QUERY_SIZE)
const loadMore = useCallback( const loadMore = useCallback(
({ onComplete }: { onComplete?: () => void }) => { ({ onComplete }: { onComplete?: () => void }) => {
if (loadingMoreV4.current || loadingMoreV3.current || loadingMoreV2.current) { if (loadingMoreV3.current || loadingMoreV2.current) {
return return
} }
loadingMoreV4.current = true
loadingMoreV3.current = true loadingMoreV3.current = true
loadingMoreV2.current = true loadingMoreV2.current = true
querySizeRef.current += ALL_TX_DEFAULT_QUERY_SIZE querySizeRef.current += ALL_TX_DEFAULT_QUERY_SIZE
fetchMoreV4({
variables: {
cursor: dataV4?.v4Transactions?.[dataV4.v4Transactions.length - 1]?.timestamp,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev
}
if (!loadingMoreV3.current && !loadingMoreV2.current) {
onComplete?.()
}
const mergedData = {
v4Transactions: [...(prev.v4Transactions ?? []), ...(fetchMoreResult.v4Transactions ?? [])],
}
loadingMoreV4.current = false
return mergedData
},
})
fetchMoreV3({ fetchMoreV3({
variables: { variables: {
cursor: dataV3?.v3Transactions?.[dataV3.v3Transactions.length - 1]?.timestamp, cursor: dataV3?.v3Transactions?.[dataV3.v3Transactions.length - 1]?.timestamp,
...@@ -98,7 +66,7 @@ export function useAllTransactions( ...@@ -98,7 +66,7 @@ export function useAllTransactions(
if (!fetchMoreResult) { if (!fetchMoreResult) {
return prev return prev
} }
if (!loadingMoreV2.current && !loadingMoreV4.current) { if (!loadingMoreV2.current) {
onComplete?.() onComplete?.()
} }
const mergedData = { const mergedData = {
...@@ -117,9 +85,7 @@ export function useAllTransactions( ...@@ -117,9 +85,7 @@ export function useAllTransactions(
if (!fetchMoreResult) { if (!fetchMoreResult) {
return prev return prev
} }
if (!loadingMoreV3.current && !loadingMoreV4.current) { !loadingMoreV3.current && onComplete?.()
onComplete?.()
}
const mergedData = { const mergedData = {
v2Transactions: [...(prev.v2Transactions ?? []), ...(fetchMoreResult.v2Transactions ?? [])], v2Transactions: [...(prev.v2Transactions ?? []), ...(fetchMoreResult.v2Transactions ?? [])],
} }
...@@ -128,30 +94,29 @@ export function useAllTransactions( ...@@ -128,30 +94,29 @@ export function useAllTransactions(
}, },
}) })
}, },
[dataV2?.v2Transactions, dataV3?.v3Transactions, dataV4?.v4Transactions, fetchMoreV2, fetchMoreV3, fetchMoreV4], [dataV2?.v2Transactions, dataV3?.v3Transactions, fetchMoreV2, fetchMoreV3],
)
const filterTransaction = useCallback(
(tx: PoolTxFragment | undefined): tx is PoolTxFragment => {
return !!tx?.type && filter.includes(BETypeToTransactionType[tx.type])
},
[filter],
) )
const transactions: PoolTxFragment[] = useMemo(() => { const transactions: PoolTransaction[] = useMemo(() => {
return [...(dataV4?.v4Transactions ?? []), ...(dataV3?.v3Transactions ?? []), ...(dataV2?.v2Transactions ?? [])] const v3Transactions =
.filter(filterTransaction) dataV3?.v3Transactions?.filter(
(tx): tx is PoolTransaction => tx.type && filter.includes(BETypeToTransactionType[tx.type]),
) ?? []
const v2Transactions =
dataV2?.v2Transactions?.filter(
(tx): tx is PoolTransaction => tx !== undefined && tx.type && filter.includes(BETypeToTransactionType[tx.type]),
) ?? []
return [...v3Transactions, ...v2Transactions]
.sort((a, b) => (b?.timestamp || 0) - (a?.timestamp || 0)) .sort((a, b) => (b?.timestamp || 0) - (a?.timestamp || 0))
.slice(0, querySizeRef.current) .slice(0, querySizeRef.current)
}, [dataV2?.v2Transactions, dataV3?.v3Transactions, dataV4?.v4Transactions, filterTransaction]) }, [dataV2?.v2Transactions, dataV3?.v3Transactions, filter])
return { return {
transactions, transactions,
// useIsWindowVisible briefly initializes as false, which skips the GQL transaction query, so the "no data found" state initially flashes // useIsWindowVisible briefly initializes as false, which skips the GQL transaction query, so the "no data found" state initially flashes
loading: loadingV2 || loadingV3 || loadingV4 || !isWindowVisible, loading: loadingV2 || loadingV3 || !isWindowVisible,
errorV2, errorV2,
errorV3, errorV3,
errorV4,
loadMore, loadMore,
} }
} }
...@@ -3,10 +3,8 @@ import { ...@@ -3,10 +3,8 @@ import {
Chain, Chain,
PoolTransaction, PoolTransaction,
PoolTransactionType, PoolTransactionType,
PoolTxFragment,
useV2TokenTransactionsQuery, useV2TokenTransactionsQuery,
useV3TokenTransactionsQuery, useV3TokenTransactionsQuery,
useV4TokenTransactionsQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
...@@ -25,18 +23,6 @@ export function useTokenTransactions( ...@@ -25,18 +23,6 @@ export function useTokenTransactions(
filter: TokenTransactionType[] = [TokenTransactionType.BUY, TokenTransactionType.SELL], filter: TokenTransactionType[] = [TokenTransactionType.BUY, TokenTransactionType.SELL],
) { ) {
const { defaultChainId } = useEnabledChains() const { defaultChainId } = useEnabledChains()
const {
data: dataV4,
loading: loadingV4,
fetchMore: fetchMoreV4,
error: errorV4,
} = useV4TokenTransactionsQuery({
variables: {
address: address.toLowerCase(),
chain: toGraphQLChain(chainId ?? defaultChainId),
first: TokenTransactionDefaultQuerySize,
},
})
const { const {
data: dataV3, data: dataV3,
loading: loadingV3, loading: loadingV3,
...@@ -61,42 +47,17 @@ export function useTokenTransactions( ...@@ -61,42 +47,17 @@ export function useTokenTransactions(
chain: toGraphQLChain(chainId), chain: toGraphQLChain(chainId),
}, },
}) })
const loadingMoreV4 = useRef(false)
const loadingMoreV3 = useRef(false) const loadingMoreV3 = useRef(false)
const loadingMoreV2 = useRef(false) const loadingMoreV2 = useRef(false)
const querySizeRef = useRef(TokenTransactionDefaultQuerySize) const querySizeRef = useRef(TokenTransactionDefaultQuerySize)
const loadMore = useCallback( const loadMore = useCallback(
({ onComplete }: { onComplete?: () => void }) => { ({ onComplete }: { onComplete?: () => void }) => {
if (loadingMoreV4.current || loadingMoreV3.current || loadingMoreV2.current) { if (loadingMoreV3.current || loadingMoreV2.current) {
return return
} }
loadingMoreV4.current = true
loadingMoreV3.current = true loadingMoreV3.current = true
loadingMoreV2.current = true loadingMoreV2.current = true
querySizeRef.current += TokenTransactionDefaultQuerySize querySizeRef.current += TokenTransactionDefaultQuerySize
fetchMoreV4({
variables: {
cursor: dataV4?.token?.v4Transactions?.[dataV4.token?.v4Transactions.length - 1]?.timestamp,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev
}
if (!loadingMoreV3.current && !loadingMoreV2.current) {
onComplete?.()
}
const mergedData = {
token: {
...prev.token,
id: prev?.token?.id ?? '',
chain: prev?.token?.chain ?? Chain.Ethereum,
v4Transactions: [...(prev.token?.v4Transactions ?? []), ...(fetchMoreResult.token?.v4Transactions ?? [])],
},
}
loadingMoreV4.current = false
return mergedData
},
})
fetchMoreV3({ fetchMoreV3({
variables: { variables: {
cursor: dataV3?.token?.v3Transactions?.[dataV3.token?.v3Transactions.length - 1]?.timestamp, cursor: dataV3?.token?.v3Transactions?.[dataV3.token?.v3Transactions.length - 1]?.timestamp,
...@@ -105,7 +66,7 @@ export function useTokenTransactions( ...@@ -105,7 +66,7 @@ export function useTokenTransactions(
if (!fetchMoreResult) { if (!fetchMoreResult) {
return prev return prev
} }
if (!loadingMoreV2.current && !loadingMoreV4.current) { if (!loadingMoreV2.current) {
onComplete?.() onComplete?.()
} }
const mergedData = { const mergedData = {
...@@ -128,7 +89,7 @@ export function useTokenTransactions( ...@@ -128,7 +89,7 @@ export function useTokenTransactions(
if (!fetchMoreResult) { if (!fetchMoreResult) {
return prev return prev
} }
if (!loadingMoreV3.current && !loadingMoreV4.current) { if (!loadingMoreV3.current) {
onComplete?.() onComplete?.()
} }
const mergedData = { const mergedData = {
...@@ -144,55 +105,49 @@ export function useTokenTransactions( ...@@ -144,55 +105,49 @@ export function useTokenTransactions(
}, },
}) })
}, },
[ [dataV2?.token?.v2Transactions, dataV3?.token?.v3Transactions, fetchMoreV2, fetchMoreV3],
dataV2?.token?.v2Transactions,
dataV3?.token?.v3Transactions,
dataV4?.token?.v4Transactions,
fetchMoreV2,
fetchMoreV3,
fetchMoreV4,
],
)
const filterTransaction = useCallback(
(tx: PoolTxFragment | undefined) => {
if (!tx) {
return false
}
const tokenBeingSold = parseFloat(tx.token0Quantity) > 0 ? tx.token0 : tx.token1
const isSell = tokenBeingSold.address?.toLowerCase() === address.toLowerCase()
return (
tx.type === PoolTransactionType.Swap &&
filter.includes(isSell ? TokenTransactionType.SELL : TokenTransactionType.BUY)
)
},
[address, filter],
) )
const transactions = useMemo( const transactions = useMemo(
() => () =>
[ [
...(dataV4?.token?.v4Transactions ?? []), ...(dataV3?.token?.v3Transactions?.filter((tx) => {
...(dataV3?.token?.v3Transactions ?? []), if (!tx) {
...(dataV2?.token?.v2Transactions ?? []), return false
}
const tokenBeingSold = parseFloat(tx.token0Quantity) > 0 ? tx.token0 : tx.token1
const isSell = tokenBeingSold.address?.toLowerCase() === address.toLowerCase()
return (
tx.type === PoolTransactionType.Swap &&
filter.includes(isSell ? TokenTransactionType.SELL : TokenTransactionType.BUY)
)
}) ?? []),
...(dataV2?.token?.v2Transactions?.filter((tx) => {
if (!tx) {
return false
}
const tokenBeingSold = parseFloat(tx.token0Quantity) > 0 ? tx.token0 : tx.token1
const isSell = tokenBeingSold.address?.toLowerCase() === address.toLowerCase()
return (
tx.type === PoolTransactionType.Swap &&
filter.includes(isSell ? TokenTransactionType.SELL : TokenTransactionType.BUY)
)
}) ?? []),
] ]
.filter(filterTransaction)
.sort((a, b): number => .sort((a, b): number =>
a?.timestamp && b?.timestamp ? b.timestamp - a.timestamp : a?.timestamp === null ? -1 : 1, a?.timestamp && b?.timestamp ? b.timestamp - a.timestamp : a?.timestamp === null ? -1 : 1,
) )
.slice(0, querySizeRef.current), .slice(0, querySizeRef.current),
[dataV2?.token?.v2Transactions, dataV3?.token?.v3Transactions, dataV4?.token?.v4Transactions, filterTransaction], [address, dataV2?.token?.v2Transactions, dataV3?.token?.v3Transactions, filter],
) )
return useMemo( return useMemo(() => {
() => ({ return {
transactions: transactions as PoolTransaction[], transactions: transactions as PoolTransaction[],
loading: loadingV4 || loadingV3 || loadingV2, loading: loadingV3 || loadingV2,
loadMore, loadMore,
errorV2, errorV2,
errorV3, errorV3,
errorV4, }
}), }, [transactions, loadingV3, loadingV2, loadMore, errorV2, errorV3])
[transactions, loadingV4, loadingV3, loadingV2, loadMore, errorV2, errorV3, errorV4],
)
} }
...@@ -13,7 +13,7 @@ export function clientToProvider(client?: Client<Transport, UniverseChainInfo>, ...@@ -13,7 +13,7 @@ export function clientToProvider(client?: Client<Transport, UniverseChainInfo>,
} }
const { chain, transport } = client const { chain, transport } = client
const ensAddress = chain?.contracts?.ensRegistry?.address const ensAddress = chain.contracts?.ensRegistry?.address
const network = chain const network = chain
? { ? {
chainId: chain.id, chainId: chain.id,
......
...@@ -86,7 +86,7 @@ function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) { ...@@ -86,7 +86,7 @@ function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) {
const { value: lpRedesignEnabled, isLoading: isLPRedesignGateLoading } = useFeatureFlagWithLoading( const { value: lpRedesignEnabled, isLoading: isLPRedesignGateLoading } = useFeatureFlagWithLoading(
FeatureFlags.LPRedesign, FeatureFlags.LPRedesign,
) )
const isMigrateToV4Enabled = useFeatureFlag(FeatureFlags.MigrateV3ToV4) const isMigrateEnabled = useFeatureFlag(FeatureFlags.MigrateV3ToV4)
const [transactionSteps, setTransactionSteps] = useState<TransactionStep[]>([]) const [transactionSteps, setTransactionSteps] = useState<TransactionStep[]>([])
const [currentTransactionStep, setCurrentTransactionStep] = useState< const [currentTransactionStep, setCurrentTransactionStep] = useState<
...@@ -113,7 +113,7 @@ function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) { ...@@ -113,7 +113,7 @@ function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) {
return <Navigate to="/pools" replace /> return <Navigate to="/pools" replace />
} }
if (!isMigrateToV4Enabled || !isSameAddress(account?.address, owner)) { if (!isMigrateEnabled || !isSameAddress(account?.address, owner)) {
navigate('/positions') navigate('/positions')
} }
......
...@@ -29,7 +29,6 @@ import { DeprecatedButton, Flex, Main, Switch, Text, styled } from 'ui/src' ...@@ -29,7 +29,6 @@ import { DeprecatedButton, Flex, Main, Switch, Text, styled } from 'ui/src'
import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled' import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled'
import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition' import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { useSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag, useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag, useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks'
...@@ -120,7 +119,6 @@ function PositionPage() { ...@@ -120,7 +119,6 @@ function PositionPage() {
const chainId = useChainIdFromUrlParam() const chainId = useChainIdFromUrlParam()
const chainInfo = chainId ? getChainInfo(chainId) : undefined const chainInfo = chainId ? getChainInfo(chainId) : undefined
const account = useAccount() const account = useAccount()
const supportedAccountChainId = useSupportedChainId(account.chainId)
const { pathname } = useLocation() const { pathname } = useLocation()
const { const {
data, data,
...@@ -134,7 +132,7 @@ function PositionPage() { ...@@ -134,7 +132,7 @@ function PositionPage() {
? ProtocolVersion.V4 ? ProtocolVersion.V4
: ProtocolVersion.UNSPECIFIED, : ProtocolVersion.UNSPECIFIED,
tokenId: tokenIdFromUrl, tokenId: tokenIdFromUrl,
chainId: chainId ?? supportedAccountChainId, chainId: chainId ?? account.chainId,
}) })
const position = data?.position const position = data?.position
const positionInfo = useMemo(() => parseRestPosition(position), [position]) const positionInfo = useMemo(() => parseRestPosition(position), [position])
...@@ -146,7 +144,7 @@ function PositionPage() { ...@@ -146,7 +144,7 @@ function PositionPage() {
const { value: lpRedesignEnabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.LPRedesign) const { value: lpRedesignEnabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.LPRedesign)
const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data) const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data)
const isMigrateToV4Enabled = useFeatureFlag(FeatureFlags.MigrateV3ToV4) const isMigrateEnabled = useFeatureFlag(FeatureFlags.MigrateV3ToV4)
const { formatCurrencyAmount } = useFormatter() const { formatCurrencyAmount } = useFormatter()
const navigate = useNavigate() const navigate = useNavigate()
...@@ -257,7 +255,7 @@ function PositionPage() { ...@@ -257,7 +255,7 @@ function PositionPage() {
{positionInfo.version === ProtocolVersion.V3 && {positionInfo.version === ProtocolVersion.V3 &&
status !== PositionStatus.CLOSED && status !== PositionStatus.CLOSED &&
isV4DataEnabled && isV4DataEnabled &&
isMigrateToV4Enabled && ( isMigrateEnabled && (
<MouseoverTooltip <MouseoverTooltip
text={t('pool.migrateLiquidityDisabledTooltip')} text={t('pool.migrateLiquidityDisabledTooltip')}
disabled={!showV4UnsupportedTooltip} disabled={!showV4UnsupportedTooltip}
......
...@@ -3,6 +3,7 @@ import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pool ...@@ -3,6 +3,7 @@ import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pool
import { DropdownSelector } from 'components/DropdownSelector' import { DropdownSelector } from 'components/DropdownSelector'
import { lpStatusConfig } from 'components/Liquidity/constants' import { lpStatusConfig } from 'components/Liquidity/constants'
import { getProtocolStatusLabel, getProtocolVersionLabel } from 'components/Liquidity/utils' import { getProtocolStatusLabel, getProtocolVersionLabel } from 'components/Liquidity/utils'
import { useAccount } from 'hooks/useAccount'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
...@@ -49,6 +50,7 @@ export function PositionsHeader({ ...@@ -49,6 +50,7 @@ export function PositionsHeader({
onStatusChange, onStatusChange,
}: PositionsHeaderProps) { }: PositionsHeaderProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { isConnected } = useAccount()
const { chains } = useEnabledChains() const { chains } = useEnabledChains()
const navigate = useNavigate() const navigate = useNavigate()
const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data) const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data)
...@@ -137,107 +139,109 @@ export function PositionsHeader({ ...@@ -137,107 +139,109 @@ export function PositionsHeader({
return ( return (
<Flex gap={16}> <Flex gap={16}>
<Text variant="heading3">{t('pool.positions.title')}</Text> <Text variant="heading3">{t('pool.positions.title')}</Text>
<Flex gap="$gap8" row $sm={{ flexDirection: 'column' }}> {isConnected && (
<Flex gap="$spacing1" row> <Flex gap="$gap8" row $sm={{ flexDirection: 'column' }}>
<Flex <Flex gap="$spacing1" row>
row <Flex
gap="$gap8" row
px="$padding16" gap="$gap8"
backgroundColor="$neutral1" px="$padding16"
borderTopLeftRadius="$rounded16" backgroundColor="$neutral1"
borderBottomLeftRadius="$rounded16" borderTopLeftRadius="$rounded16"
alignItems="center" borderBottomLeftRadius="$rounded16"
$sm={{ justifyContent: 'center' }} alignItems="center"
justifyContent="flex-start" $sm={{ justifyContent: 'center' }}
flex={1} justifyContent="flex-start"
{...ClickableTamaguiStyle} flex={1}
onPress={() => { {...ClickableTamaguiStyle}
navigate(`/positions/create/${isV4DataEnabled ? 'v4' : 'v3'}`) onPress={() => {
}} navigate(`/positions/create/${isV4DataEnabled ? 'v4' : 'v3'}`)
>
<Plus size={20} color="$surface1" />
<Text color="$surface1" variant="buttonLabel3">
{t('common.new')}
</Text>
</Flex>
<DropdownSelector
containerStyle={{ width: 'auto' }}
menuLabel={
<Flex
borderTopRightRadius="$rounded16"
borderBottomRightRadius="$rounded16"
backgroundColor="$neutral1"
justifyContent="center"
alignItems="center"
p="$padding8"
{...ClickableTamaguiStyle}
>
<RotatableChevron direction="down" height={20} width={20} color="$surface1" />
</Flex>
}
buttonStyle={{
borderWidth: 0,
p: 0,
}}
dropdownStyle={{
width: 160,
}}
internalMenuItems={<>{createOptions}</>}
hideChevron={true}
isOpen={createDropdownOpen}
toggleOpen={() => {
setCreateDropdownOpen((prev) => !prev)
}}
/>
</Flex>
{showFilters && (
<Flex row alignItems="center" shrink height="100%" gap="$gap4">
<DropdownSelector
isOpen={protocolDropdownOpen}
toggleOpen={() => {
setProtocolDropdownOpen((prev) => !prev)
}}
menuLabel={<Text variant="buttonLabel3">{t('common.status')}</Text>}
internalMenuItems={<>{statusFilterOptions}</>}
dropdownStyle={{
width: 240,
className: 'scrollbar-hidden',
}} }}
buttonStyle={StyledDropdownButton} >
/> <Plus size={20} color="$surface1" />
<Text color="$surface1" variant="buttonLabel3">
{t('common.new')}
</Text>
</Flex>
<DropdownSelector <DropdownSelector
isOpen={statusDropdownOpen} containerStyle={{ width: 'auto' }}
toggleOpen={() => setStatusDropdownOpen((prev) => !prev)} menuLabel={
menuLabel={<Text variant="buttonLabel3">{t('common.protocol')}</Text>} <Flex
internalMenuItems={<>{versionFilterOptions}</>} borderTopRightRadius="$rounded16"
borderBottomRightRadius="$rounded16"
backgroundColor="$neutral1"
justifyContent="center"
alignItems="center"
p="$padding8"
{...ClickableTamaguiStyle}
>
<RotatableChevron direction="down" height={20} width={20} color="$surface1" />
</Flex>
}
buttonStyle={{
borderWidth: 0,
p: 0,
}}
dropdownStyle={{ dropdownStyle={{
width: 160, width: 160,
className: 'scrollbar-hidden',
}} }}
buttonStyle={StyledDropdownButton} internalMenuItems={<>{createOptions}</>}
hideChevron={true}
isOpen={createDropdownOpen}
toggleOpen={() => {
setCreateDropdownOpen((prev) => !prev)
}}
/> />
<Flex </Flex>
alignItems="center" {showFilters && (
justifyContent="center" <Flex row alignItems="center" shrink height="100%" gap="$gap4">
backgroundColor="$surface3" <DropdownSelector
borderRadius="$rounded16" isOpen={protocolDropdownOpen}
px="$padding12" toggleOpen={() => {
height="100%" setProtocolDropdownOpen((prev) => !prev)
{...ClickableTamaguiStyle}
>
<NetworkFilter
includeAllNetworks
selectedChain={selectedChain}
onPressChain={onChainChange}
chainIds={chains}
styles={{
buttonPaddingY: '$spacing8',
}} }}
menuLabel={<Text variant="buttonLabel3">{t('common.status')}</Text>}
internalMenuItems={<>{statusFilterOptions}</>}
dropdownStyle={{
width: 240,
className: 'scrollbar-hidden',
}}
buttonStyle={StyledDropdownButton}
/>
<DropdownSelector
isOpen={statusDropdownOpen}
toggleOpen={() => setStatusDropdownOpen((prev) => !prev)}
menuLabel={<Text variant="buttonLabel3">{t('common.protocol')}</Text>}
internalMenuItems={<>{versionFilterOptions}</>}
dropdownStyle={{
width: 160,
className: 'scrollbar-hidden',
}}
buttonStyle={StyledDropdownButton}
/> />
<Flex
alignItems="center"
justifyContent="center"
backgroundColor="$surface3"
borderRadius="$rounded16"
px="$padding12"
height="100%"
{...ClickableTamaguiStyle}
>
<NetworkFilter
includeAllNetworks
selectedChain={selectedChain}
onPressChain={onChainChange}
chainIds={chains}
styles={{
buttonPaddingY: '$spacing8',
}}
/>
</Flex>
</Flex> </Flex>
</Flex> )}
)} </Flex>
</Flex> )}
</Flex> </Flex>
) )
} }
...@@ -23,7 +23,6 @@ import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks ...@@ -23,7 +23,6 @@ import { usePendingLPTransactionsChangeListener } from 'state/transactions/hooks
import { usePairAdder } from 'state/user/hooks' import { usePairAdder } from 'state/user/hooks'
import { Circle, DeprecatedButton, Flex, Main, Shine, Text, styled } from 'ui/src' import { Circle, DeprecatedButton, Flex, Main, Shine, Text, styled } from 'ui/src'
import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition' import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition'
import { useSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks'
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
...@@ -80,7 +79,6 @@ function V2PositionPage() { ...@@ -80,7 +79,6 @@ function V2PositionPage() {
const { pairAddress } = useParams<{ pairAddress: string }>() const { pairAddress } = useParams<{ pairAddress: string }>()
const chainId = useChainIdFromUrlParam() const chainId = useChainIdFromUrlParam()
const account = useAccount() const account = useAccount()
const supportedAccountChainId = useSupportedChainId(account.chainId)
const { const {
data, data,
...@@ -90,7 +88,7 @@ function V2PositionPage() { ...@@ -90,7 +88,7 @@ function V2PositionPage() {
owner: account?.address ?? ZERO_ADDRESS, owner: account?.address ?? ZERO_ADDRESS,
protocolVersion: ProtocolVersion.V2, protocolVersion: ProtocolVersion.V2,
pairAddress, pairAddress,
chainId: chainId ?? supportedAccountChainId, chainId: chainId ?? account.chainId,
}) })
const position = data?.position const position = data?.position
const positionInfo = useMemo(() => parseRestPosition(position), [position]) const positionInfo = useMemo(() => parseRestPosition(position), [position])
......
...@@ -262,11 +262,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod ...@@ -262,11 +262,7 @@ export function CreateTxContextProvider({ children }: { children: React.ReactNod
} }
const actualGasFee = createCalldata?.gasFee const actualGasFee = createCalldata?.gasFee
const needsApprovals = !!(approvalCalldata?.token0Approval || approvalCalldata?.token1Approval) const { value: calculatedGasFee } = useTransactionGasFee(createCalldata?.create, !!actualGasFee)
const { value: calculatedGasFee } = useTransactionGasFee(
createCalldata?.create,
!!actualGasFee || needsApprovals /* skip */,
)
const increaseGasFeeUsd = useUSDCurrencyAmountOfGasFee( const increaseGasFeeUsd = useUSDCurrencyAmountOfGasFee(
createCalldata?.create?.chainId, createCalldata?.create?.chainId,
actualGasFee || calculatedGasFee, actualGasFee || calculatedGasFee,
......
...@@ -248,11 +248,7 @@ export function CreatePositionModal({ isOpen, onClose }: { isOpen: boolean; onCl ...@@ -248,11 +248,7 @@ export function CreatePositionModal({ isOpen, onClose }: { isOpen: boolean; onCl
</Text> </Text>
<BaseQuoteFiatAmount <BaseQuoteFiatAmount
variant="body1" variant="body1"
price={ price={derivedPriceRangeInfo?.price}
derivedPriceRangeInfo.invertPrice
? derivedPriceRangeInfo.price?.invert()
: derivedPriceRangeInfo.price
}
base={baseCurrency} base={baseCurrency}
quote={quoteCurrency} quote={quoteCurrency}
/> />
......
...@@ -7,7 +7,6 @@ import { BaseQuoteFiatAmount } from 'pages/Pool/Positions/create/BaseQuoteFiatAm ...@@ -7,7 +7,6 @@ import { BaseQuoteFiatAmount } from 'pages/Pool/Positions/create/BaseQuoteFiatAm
import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext' import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext'
import { PoolOutOfSyncError } from 'pages/Pool/Positions/create/PoolOutOfSyncError' import { PoolOutOfSyncError } from 'pages/Pool/Positions/create/PoolOutOfSyncError'
import { Container } from 'pages/Pool/Positions/create/shared' import { Container } from 'pages/Pool/Positions/create/shared'
import { CreatePositionInfo, PriceRangeState } from 'pages/Pool/Positions/create/types'
import { getInvertedTuple } from 'pages/Pool/Positions/create/utils' import { getInvertedTuple } from 'pages/Pool/Positions/create/utils'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { Minus, Plus } from 'react-feather' import { Minus, Plus } from 'react-feather'
...@@ -439,27 +438,6 @@ export const SelectPriceRangeStep = ({ ...@@ -439,27 +438,6 @@ export const SelectPriceRangeStep = ({
[priceRangeState.fullRange, setPriceRangeState], [priceRangeState.fullRange, setPriceRangeState],
) )
const { rangeInputMinPrice, rangeInputMaxPrice } = useMemo(() => {
if (priceRangeState.fullRange) {
return {
rangeInputMinPrice: undefined,
rangeInputMaxPrice: undefined,
}
}
if (invertPrice) {
return {
rangeInputMinPrice: prices?.[1] ? parseFloat(prices?.[1].invert().toSignificant(8)) : undefined,
rangeInputMaxPrice: prices?.[0] ? parseFloat(prices?.[0].invert().toSignificant(8)) : undefined,
}
}
return {
rangeInputMinPrice: prices?.[0] ? parseFloat(prices?.[0].toSignificant(8)) : undefined,
rangeInputMaxPrice: prices?.[1] ? parseFloat(prices?.[1].toSignificant(8)) : undefined,
}
}, [priceRangeState.fullRange, prices, invertPrice])
const invalidState = const invalidState =
onDisableContinue || onDisableContinue ||
invalidPrice || invalidPrice ||
...@@ -495,7 +473,6 @@ export const SelectPriceRangeStep = ({ ...@@ -495,7 +473,6 @@ export const SelectPriceRangeStep = ({
} }
const showIncrementButtons = !!derivedPositionInfo.pool && !priceRangeState.fullRange const showIncrementButtons = !!derivedPositionInfo.pool && !priceRangeState.fullRange
return ( return (
<Container {...rest}> <Container {...rest}>
{creatingPoolOrPair && <InitialPriceInput />} {creatingPoolOrPair && <InitialPriceInput />}
...@@ -557,7 +534,6 @@ export const SelectPriceRangeStep = ({ ...@@ -557,7 +534,6 @@ export const SelectPriceRangeStep = ({
)} )}
{isPriceRangeInputV2Enabled && baseCurrency && quoteCurrency && derivedPositionInfo.poolId && ( {isPriceRangeInputV2Enabled && baseCurrency && quoteCurrency && derivedPositionInfo.poolId && (
<LiquidityRangeInput <LiquidityRangeInput
key={buildRangeInputKey({ derivedPositionInfo, priceRangeState })}
currency0={quoteCurrency} currency0={quoteCurrency}
currency1={baseCurrency} currency1={baseCurrency}
feeTier={fee.feeAmount} feeTier={fee.feeAmount}
...@@ -566,8 +542,16 @@ export const SelectPriceRangeStep = ({ ...@@ -566,8 +542,16 @@ export const SelectPriceRangeStep = ({
protocolVersion={derivedPositionInfo.protocolVersion} protocolVersion={derivedPositionInfo.protocolVersion}
poolId={derivedPositionInfo.poolId} poolId={derivedPositionInfo.poolId}
disableBrushInteraction={priceRangeState.fullRange} disableBrushInteraction={priceRangeState.fullRange}
minPrice={rangeInputMinPrice} minPrice={
maxPrice={rangeInputMaxPrice} priceRangeState.fullRange
? undefined
: parseFloat((invertPrice ? prices?.[0]?.invert() : prices?.[0])?.toSignificant(8) ?? '0')
}
maxPrice={
priceRangeState.fullRange
? undefined
: parseFloat((invertPrice ? prices?.[1]?.invert() : prices?.[1])?.toSignificant(8) ?? '0')
}
setMinPrice={(minPrice?: number) => { setMinPrice={(minPrice?: number) => {
handleChartRangeInput(RangeSelectionInput.MIN, minPrice?.toString()) handleChartRangeInput(RangeSelectionInput.MIN, minPrice?.toString())
}} }}
...@@ -627,13 +611,3 @@ export const SelectPriceRangeStep = ({ ...@@ -627,13 +611,3 @@ export const SelectPriceRangeStep = ({
</Container> </Container>
) )
} }
function buildRangeInputKey({
derivedPositionInfo,
priceRangeState,
}: {
derivedPositionInfo: CreatePositionInfo
priceRangeState: PriceRangeState
}) {
return `${derivedPositionInfo.poolId}-${priceRangeState.fullRange}-${priceRangeState.priceInverted}-${derivedPositionInfo.protocolVersion}`
}
...@@ -247,7 +247,7 @@ export function SelectTokensStep({ ...@@ -247,7 +247,7 @@ export function SelectTokensStep({
hook: hook ?? ZERO_ADDRESS, hook: hook ?? ZERO_ADDRESS,
}) })
const feeTiers = getDefaultFeeTiersWithData({ chainId: token0?.chainId, feeTierData, protocolVersion, t }) const feeTiers = getDefaultFeeTiersWithData({ chainId: token0?.chainId, feeTierData, t })
const [defaultFeeTierSelected, setDefaultFeeTierSelected] = useState(false) const [defaultFeeTierSelected, setDefaultFeeTierSelected] = useState(false)
const mostUsedFeeTier = useMemo(() => { const mostUsedFeeTier = useMemo(() => {
if (hasExistingFeeTiers && feeTierData && Object.keys(feeTierData).length > 0) { if (hasExistingFeeTiers && feeTierData && Object.keys(feeTierData).length > 0) {
......
...@@ -54,7 +54,6 @@ import { useUrlContext } from 'uniswap/src/contexts/UrlContext' ...@@ -54,7 +54,6 @@ import { useUrlContext } from 'uniswap/src/contexts/UrlContext'
import { useGetPoolsByTokens } from 'uniswap/src/data/rest/getPools' import { useGetPoolsByTokens } from 'uniswap/src/data/rest/getPools'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { useSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId' import { useSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { useMaxAmountSpend } from 'uniswap/src/features/gas/useMaxAmountSpend'
import { useOnChainCurrencyBalance } from 'uniswap/src/features/portfolio/api' import { useOnChainCurrencyBalance } from 'uniswap/src/features/portfolio/api'
import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice'
import { getValidAddress } from 'uniswap/src/utils/addresses' import { getValidAddress } from 'uniswap/src/utils/addresses'
...@@ -356,8 +355,6 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -356,8 +355,6 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
const { balance: token0Balance } = useOnChainCurrencyBalance(token0, address) const { balance: token0Balance } = useOnChainCurrencyBalance(token0, address)
const { balance: token1Balance } = useOnChainCurrencyBalance(token1, address) const { balance: token1Balance } = useOnChainCurrencyBalance(token1, address)
const token0MaxAmount = useMaxAmountSpend({ currencyAmount: token0Balance })
const token1MaxAmount = useMaxAmountSpend({ currencyAmount: token1Balance })
const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0] const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0]
const independentAmount = tryParseCurrencyAmount(exactAmounts[exactField], independentToken) const independentAmount = tryParseCurrencyAmount(exactAmounts[exactField], independentToken)
...@@ -432,8 +429,8 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -432,8 +429,8 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
return t('common.noAmount.error') return t('common.noAmount.error')
} }
const insufficientToken0Balance = currency0Amount && token0MaxAmount?.lessThan(currency0Amount) const insufficientToken0Balance = currency0Amount && token0Balance?.lessThan(currency0Amount)
const insufficientToken1Balance = currency1Amount && token1MaxAmount?.lessThan(currency1Amount) const insufficientToken1Balance = currency1Amount && token1Balance?.lessThan(currency1Amount)
if (insufficientToken0Balance && insufficientToken1Balance) { if (insufficientToken0Balance && insufficientToken1Balance) {
return <Trans i18nKey="common.insufficientBalance.error" /> return <Trans i18nKey="common.insufficientBalance.error" />
...@@ -468,9 +465,9 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { ...@@ -468,9 +465,9 @@ export function useDepositInfo(state: UseDepositInfoProps): DepositInfo {
deposit0Disabled, deposit0Disabled,
deposit1Disabled, deposit1Disabled,
currency0Amount, currency0Amount,
token0MaxAmount, token0Balance,
currency1Amount, currency1Amount,
token1MaxAmount, token1Balance,
t, t,
token0?.symbol, token0?.symbol,
token1?.symbol, token1?.symbol,
......
...@@ -108,7 +108,6 @@ type BasePriceRangeInfo = { ...@@ -108,7 +108,6 @@ type BasePriceRangeInfo = {
deposit0Disabled: boolean deposit0Disabled: boolean
deposit1Disabled: boolean deposit1Disabled: boolean
price?: Price<Currency, Currency> price?: Price<Currency, Currency>
invertPrice: boolean
} }
export type OptionalCurrencyPrice = Price<Currency, Currency> | undefined export type OptionalCurrencyPrice = Price<Currency, Currency> | undefined
...@@ -118,6 +117,7 @@ type BasePoolPriceRangeInfo = { ...@@ -118,6 +117,7 @@ type BasePoolPriceRangeInfo = {
ticksAtLimit: [boolean, boolean] ticksAtLimit: [boolean, boolean]
tickSpaceLimits: [OptionalNumber, OptionalNumber] tickSpaceLimits: [OptionalNumber, OptionalNumber]
isSorted: boolean isSorted: boolean
invertPrice: boolean
invalidPrice: boolean invalidPrice: boolean
invalidRange: boolean invalidRange: boolean
outOfRange: boolean outOfRange: boolean
......
...@@ -395,11 +395,22 @@ function createMockV4Pool({ ...@@ -395,11 +395,22 @@ function createMockV4Pool({
return pool return pool
} }
function createMockPair(price?: Price<Currency, Currency>) { function createMockPair({
if (price) { baseCurrency,
quoteCurrency,
price,
}: {
baseCurrency?: Currency
quoteCurrency?: Currency
price?: Price<Currency, Currency>
}) {
const baseToken = baseCurrency?.wrapped
const quoteToken = quoteCurrency?.wrapped
if (baseToken && quoteToken && price) {
return new Pair( return new Pair(
CurrencyAmount.fromRawAmount(price.quoteCurrency.wrapped, price.numerator), CurrencyAmount.fromRawAmount(baseToken, price.numerator),
CurrencyAmount.fromRawAmount(price.baseCurrency.wrapped, price.denominator), CurrencyAmount.fromRawAmount(quoteToken, price.denominator),
) )
} else { } else {
return undefined return undefined
...@@ -519,27 +530,25 @@ export function getV2PriceRangeInfo({ ...@@ -519,27 +530,25 @@ export function getV2PriceRangeInfo({
const { currencies } = derivedPositionInfo const { currencies } = derivedPositionInfo
const [baseCurrency] = getInvertedTuple(currencies, state.priceInverted) const [baseCurrency] = getInvertedTuple(currencies, state.priceInverted)
const baseToken = getCurrencyWithWrap(baseCurrency, ProtocolVersion.V2)
const sortedTokens = getSortedCurrenciesTuple(
getCurrencyWithWrap(currencies[0], ProtocolVersion.V2),
getCurrencyWithWrap(currencies[1], ProtocolVersion.V2),
)
const price = getInitialPrice({ const price = getInitialPrice({
baseCurrency: baseToken, baseCurrency: getCurrencyWithWrap(baseCurrency, ProtocolVersion.V2),
sortedCurrencies: sortedTokens, sortedCurrencies: getSortedCurrenciesTuple(
getCurrencyWithWrap(currencies[0], ProtocolVersion.V2),
getCurrencyWithWrap(currencies[1], ProtocolVersion.V2),
),
initialPrice: state.initialPrice, initialPrice: state.initialPrice,
}) })
const invertPrice = Boolean(baseToken && sortedTokens[0] && !baseToken.equals(sortedTokens[0]))
return { return {
protocolVersion: ProtocolVersion.V2, protocolVersion: ProtocolVersion.V2,
price, price,
mockPair: createMockPair(price), mockPair: createMockPair({
baseCurrency: currencies[0],
quoteCurrency: currencies[1],
price,
}),
deposit0Disabled: false, deposit0Disabled: false,
deposit1Disabled: false, deposit1Disabled: false,
invertPrice,
} satisfies V2PriceRangeInfo } satisfies V2PriceRangeInfo
} }
......
...@@ -21,7 +21,6 @@ import { ThemeProvider } from 'theme' ...@@ -21,7 +21,6 @@ import { ThemeProvider } from 'theme'
import { nativeOnChain } from 'uniswap/src/constants/tokens' import { nativeOnChain } from 'uniswap/src/constants/tokens'
import { useTokenWebQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useTokenWebQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { isAddress } from 'utilities/src/addresses' import { isAddress } from 'utilities/src/addresses'
import { useChainIdFromUrlParam } from 'utils/chainParams' import { useChainIdFromUrlParam } from 'utils/chainParams'
...@@ -160,7 +159,6 @@ export default function TokenDetailsPage() { ...@@ -160,7 +159,6 @@ export default function TokenDetailsPage() {
const pageChainId = account.chainId ?? UniverseChainId.Mainnet const pageChainId = account.chainId ?? UniverseChainId.Mainnet
const contextValue = useCreateTDPContext() const contextValue = useCreateTDPContext()
const { tokenColor, address, currency, currencyChain, currencyChainId, tokenQuery } = contextValue const { tokenColor, address, currency, currencyChain, currencyChainId, tokenQuery } = contextValue
const isSupportedChain = useIsSupportedChainId(currencyChainId)
const tokenQueryData = tokenQuery.data?.token const tokenQueryData = tokenQuery.data?.token
const metatagProperties = useMemo(() => { const metatagProperties = useMemo(() => {
...@@ -187,7 +185,7 @@ export default function TokenDetailsPage() { ...@@ -187,7 +185,7 @@ export default function TokenDetailsPage() {
))} ))}
</Helmet> </Helmet>
{(() => { {(() => {
if (currency && isSupportedChain) { if (currency) {
return ( return (
<TDPProvider contextValue={contextValue}> <TDPProvider contextValue={contextValue}>
<TokenDetails /> <TokenDetails />
......
...@@ -4,7 +4,6 @@ import { createContext, useMemo } from 'react' ...@@ -4,7 +4,6 @@ import { createContext, useMemo } from 'react'
import { ALL_NETWORKS_ARG } from 'uniswap/src/data/rest/base' import { ALL_NETWORKS_ARG } from 'uniswap/src/data/rest/base'
import { useExploreStatsQuery } from 'uniswap/src/data/rest/exploreStats' import { useExploreStatsQuery } from 'uniswap/src/data/rest/exploreStats'
import { useProtocolStatsQuery } from 'uniswap/src/data/rest/protocolStats' import { useProtocolStatsQuery } from 'uniswap/src/data/rest/protocolStats'
import { useIsSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
interface QueryResult<T> { interface QueryResult<T> {
...@@ -49,21 +48,19 @@ export function ExploreContextProvider({ ...@@ -49,21 +48,19 @@ export function ExploreContextProvider({
chainId?: UniverseChainId chainId?: UniverseChainId
children: React.ReactNode children: React.ReactNode
}) { }) {
const isSupportedChain = useIsSupportedChainId(chainId)
const { const {
data: exploreStatsData, data: exploreStatsData,
isLoading: exploreStatsLoading, isLoading: exploreStatsLoading,
error: exploreStatsError, error: exploreStatsError,
} = useExploreStatsQuery({ } = useExploreStatsQuery({
chainId: isSupportedChain ? chainId?.toString() : ALL_NETWORKS_ARG, chainId: chainId ? chainId.toString() : ALL_NETWORKS_ARG,
}) })
const { const {
data: protocolStatsData, data: protocolStatsData,
isLoading: protocolStatsLoading, isLoading: protocolStatsLoading,
error: protocolStatsError, error: protocolStatsError,
} = useProtocolStatsQuery({ } = useProtocolStatsQuery({
chainId: isSupportedChain ? chainId?.toString() : ALL_NETWORKS_ARG, chainId: chainId ? chainId.toString() : ALL_NETWORKS_ARG,
}) })
const exploreContext = useMemo(() => { const exploreContext = useMemo(() => {
......
...@@ -18,7 +18,7 @@ import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' ...@@ -18,7 +18,7 @@ import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
function useFilteredPools(pools?: PoolStat[]) { function useFilteredPools(pools?: PoolStat[]) {
const filterString = useAtomValue(exploreSearchStringAtom) const filterString = useAtomValue(exploreSearchStringAtom)
const isV4DataEnabled = useFeatureFlag(FeatureFlags.V4Data) const isV4Enabled = useFeatureFlag(FeatureFlags.V4Data)
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString]) const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
...@@ -39,10 +39,10 @@ function useFilteredPools(pools?: PoolStat[]) { ...@@ -39,10 +39,10 @@ function useFilteredPools(pools?: PoolStat[]) {
token0HashIncludesFilterString || token0HashIncludesFilterString ||
token1HashIncludesFilterString || token1HashIncludesFilterString ||
poolNameIncludesFilterString) && poolNameIncludesFilterString) &&
(pool.protocolVersion?.toLowerCase() !== 'v4' || isV4DataEnabled) (pool.protocolVersion?.toLowerCase() !== 'v4' || isV4Enabled)
) )
}), }),
[isV4DataEnabled, lowercaseFilterString, pools], [isV4Enabled, lowercaseFilterString, pools],
) )
} }
......
import { TransactionResponse } from '@ethersproject/providers'
import { LiquidityEventName } from '@uniswap/analytics-events' import { LiquidityEventName } from '@uniswap/analytics-events'
import { getLiquidityEventName } from 'components/Liquidity/analytics' import { getLiquidityEventName } from 'components/Liquidity/analytics'
import { PopupType, addPopup } from 'state/application/reducer' import { PopupType, addPopup } from 'state/application/reducer'
...@@ -111,13 +112,13 @@ function* handlePositionTransactionStep(params: HandlePositionStepParams) { ...@@ -111,13 +112,13 @@ function* handlePositionTransactionStep(params: HandlePositionStepParams) {
const info = getLiquidityTransactionInfo(action) const info = getLiquidityTransactionInfo(action)
const txRequest = yield* call(getLiquidityTxRequest, step, signature) const txRequest = yield* call(getLiquidityTxRequest, step, signature)
const onModification = ({ hash, data }: { hash: string; data: string }) => { const onModification = (response: TransactionResponse) => {
if (analytics) { if (analytics) {
sendAnalyticsEvent(LiquidityEventName.TRANSACTION_MODIFIED_IN_WALLET, { sendAnalyticsEvent(LiquidityEventName.TRANSACTION_MODIFIED_IN_WALLET, {
...analytics, ...analytics,
transaction_hash: hash, transaction_hash: response.hash,
expected: txRequest.data?.toString(), expected: txRequest.data?.toString(),
actual: data, actual: response.data,
}) })
} }
} }
......
/* eslint-disable rulesdir/no-undefined-or */ /* eslint-disable rulesdir/no-undefined-or */
import { TransactionResponse } from '@ethersproject/providers'
import { SwapEventName } from '@uniswap/analytics-events' import { SwapEventName } from '@uniswap/analytics-events'
import { ZERO_PERCENT } from 'constants/misc' import { ZERO_PERCENT } from 'constants/misc'
import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics' import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics'
...@@ -18,7 +19,6 @@ import { ...@@ -18,7 +19,6 @@ import {
handleSignatureStep, handleSignatureStep,
} from 'state/sagas/transactions/utils' } from 'state/sagas/transactions/utils'
import { handleWrapStep } from 'state/sagas/transactions/wrapSaga' import { handleWrapStep } from 'state/sagas/transactions/wrapSaga'
import { VitalTxFields } from 'state/transactions/types'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { call, put } from 'typed-redux-saga' import { call, put } from 'typed-redux-saga'
import { FetchError } from 'uniswap/src/data/apiClients/FetchError' import { FetchError } from 'uniswap/src/data/apiClients/FetchError'
...@@ -55,11 +55,11 @@ import { ...@@ -55,11 +55,11 @@ import {
ValidatedUniswapXSwapTxAndGasInfo, ValidatedUniswapXSwapTxAndGasInfo,
} from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo'
import { BridgeTrade, ClassicTrade } from 'uniswap/src/features/transactions/swap/types/trade' import { BridgeTrade, ClassicTrade } from 'uniswap/src/features/transactions/swap/types/trade'
import { slippageToleranceToPercent } from 'uniswap/src/features/transactions/swap/utils/format'
import { generateTransactionSteps } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' import { generateTransactionSteps } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps'
import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing' import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing'
import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi'
import { createSaga } from 'uniswap/src/utils/saga' import { createSaga } from 'uniswap/src/utils/saga'
import { percentFromFloat } from 'utilities/src/format/percent'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage' import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
...@@ -76,12 +76,12 @@ function* handleSwapTransactionStep(params: HandleSwapStepParams) { ...@@ -76,12 +76,12 @@ function* handleSwapTransactionStep(params: HandleSwapStepParams) {
const info = getSwapTransactionInfo(trade) const info = getSwapTransactionInfo(trade)
const txRequest = yield* call(getSwapTxRequest, step, signature) const txRequest = yield* call(getSwapTxRequest, step, signature)
const onModification = ({ hash, data }: VitalTxFields) => { const onModification = (response: TransactionResponse) => {
sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, {
...analytics, ...analytics,
txHash: hash, txHash: response.hash,
expected: txRequest.data?.toString() ?? '', expected: txRequest.data?.toString() ?? '',
actual: data, actual: response.data,
}) })
} }
...@@ -96,11 +96,12 @@ function* handleSwapTransactionStep(params: HandleSwapStepParams) { ...@@ -96,11 +96,12 @@ function* handleSwapTransactionStep(params: HandleSwapStepParams) {
onModification, onModification,
}) })
const allowedSlippage = trade.slippageTolerance ? percentFromFloat(trade.slippageTolerance) : ZERO_PERCENT
sendAnalyticsEvent( sendAnalyticsEvent(
SwapEventName.SWAP_SIGNED, SwapEventName.SWAP_SIGNED,
formatSwapSignedAnalyticsEventProperties({ formatSwapSignedAnalyticsEventProperties({
trade, trade,
allowedSlippage: trade.slippageTolerance ? slippageToleranceToPercent(trade.slippageTolerance) : ZERO_PERCENT, allowedSlippage,
fiatValues: { fiatValues: {
amountIn: analytics.token_in_amount_usd, amountIn: analytics.token_in_amount_usd,
amountOut: analytics.token_out_amount_usd, amountOut: analytics.token_out_amount_usd,
......
...@@ -20,7 +20,7 @@ import { HandledTransactionInterrupt } from 'uniswap/src/features/transactions/e ...@@ -20,7 +20,7 @@ import { HandledTransactionInterrupt } from 'uniswap/src/features/transactions/e
import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics' import { getBaseTradeAnalyticsProperties } from 'uniswap/src/features/transactions/swap/analytics'
import { UniswapXSignatureStep } from 'uniswap/src/features/transactions/swap/types/steps' import { UniswapXSignatureStep } from 'uniswap/src/features/transactions/swap/types/steps'
import { UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade' import { UniswapXTrade } from 'uniswap/src/features/transactions/swap/types/trade'
import { slippageToleranceToPercent } from 'uniswap/src/features/transactions/swap/utils/format' import { percentFromFloat } from 'utilities/src/format/percent'
interface HandleUniswapXSignatureStepParams extends HandleSignatureStepParams<UniswapXSignatureStep> { interface HandleUniswapXSignatureStepParams extends HandleSignatureStepParams<UniswapXSignatureStep> {
trade: UniswapXTrade trade: UniswapXTrade
...@@ -35,7 +35,7 @@ export function* handleUniswapXSignatureStep(params: HandleUniswapXSignatureStep ...@@ -35,7 +35,7 @@ export function* handleUniswapXSignatureStep(params: HandleUniswapXSignatureStep
const analyticsParams: Parameters<typeof formatSwapSignedAnalyticsEventProperties>[0] = { const analyticsParams: Parameters<typeof formatSwapSignedAnalyticsEventProperties>[0] = {
trade, trade,
allowedSlippage: slippageToleranceToPercent(trade.slippageTolerance), allowedSlippage: percentFromFloat(trade.slippageTolerance),
fiatValues: { fiatValues: {
amountIn: analytics.token_in_amount_usd, amountIn: analytics.token_in_amount_usd,
amountOut: analytics.token_out_amount_usd, amountOut: analytics.token_out_amount_usd,
...@@ -61,7 +61,7 @@ export function* handleUniswapXSignatureStep(params: HandleUniswapXSignatureStep ...@@ -61,7 +61,7 @@ export function* handleUniswapXSignatureStep(params: HandleUniswapXSignatureStep
SwapEventName.SWAP_SIGNED, SwapEventName.SWAP_SIGNED,
formatSwapSignedAnalyticsEventProperties({ formatSwapSignedAnalyticsEventProperties({
trade, trade,
allowedSlippage: slippageToleranceToPercent(trade.slippageTolerance), allowedSlippage: percentFromFloat(trade.slippageTolerance),
fiatValues: { fiatValues: {
amountIn: analytics.token_in_amount_usd, amountIn: analytics.token_in_amount_usd,
amountOut: analytics.token_out_amount_usd, amountOut: analytics.token_out_amount_usd,
......
import { TransactionResponse } from '@ethersproject/abstract-provider' import { JsonRpcSigner, TransactionResponse, Web3Provider } from '@ethersproject/providers'
import { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { TradeType } from '@uniswap/sdk-core' import { TradeType } from '@uniswap/sdk-core'
import { wagmiConfig } from 'components/Web3Provider/wagmiConfig' import { wagmiConfig } from 'components/Web3Provider/wagmiConfig'
import { clientToProvider } from 'hooks/useEthersProvider' import { clientToProvider } from 'hooks/useEthersProvider'
...@@ -14,15 +13,13 @@ import { ...@@ -14,15 +13,13 @@ import {
TransactionDetails, TransactionDetails,
TransactionInfo, TransactionInfo,
TransactionType, TransactionType,
VitalTxFields,
} from 'state/transactions/types' } from 'state/transactions/types'
import { isPendingTx } from 'state/transactions/utils' import { isPendingTx } from 'state/transactions/utils'
import { InterfaceState } from 'state/webReducer' import { InterfaceState } from 'state/webReducer'
import { SagaGenerator, call, cancel, delay, fork, put, race, select, take } from 'typed-redux-saga' import { SagaGenerator, call, cancel, fork, put, race, select, take } from 'typed-redux-saga'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { Routing } from 'uniswap/src/data/tradingApi/__generated__' import { Routing } from 'uniswap/src/data/tradingApi/__generated__'
import { AccountMeta } from 'uniswap/src/features/accounts/types' import { AccountMeta } from 'uniswap/src/features/accounts/types'
import { isL2ChainId } from 'uniswap/src/features/chains/utils'
import { import {
ApprovalEditedInWalletError, ApprovalEditedInWalletError,
HandledTransactionInterrupt, HandledTransactionInterrupt,
...@@ -49,8 +46,7 @@ import { Sentry } from 'utilities/src/logger/Sentry' ...@@ -49,8 +46,7 @@ import { Sentry } from 'utilities/src/logger/Sentry'
import noop from 'utilities/src/react/noop' import noop from 'utilities/src/react/noop'
import { currencyId } from 'utils/currencyId' import { currencyId } from 'utils/currencyId'
import { signTypedData } from 'utils/signing' import { signTypedData } from 'utils/signing'
import { Transaction } from 'viem' import { getConnectorClient } from 'wagmi/actions'
import { getConnectorClient, getTransaction } from 'wagmi/actions'
export interface HandleSignatureStepParams<T extends SignatureTransactionStep = SignatureTransactionStep> { export interface HandleSignatureStepParams<T extends SignatureTransactionStep = SignatureTransactionStep> {
account: AccountMeta account: AccountMeta
...@@ -96,11 +92,12 @@ export interface HandleOnChainStepParams<T extends OnChainTransactionStep = OnCh ...@@ -96,11 +92,12 @@ export interface HandleOnChainStepParams<T extends OnChainTransactionStep = OnCh
/** Controls whether the function should wait to return until after the transaction has confirmed. Defaults to `true`. */ /** Controls whether the function should wait to return until after the transaction has confirmed. Defaults to `true`. */
shouldWaitForConfirmation?: boolean shouldWaitForConfirmation?: boolean
/** Called when data returned from a submitted transaction differs from data originally sent to the wallet. */ /** Called when data returned from a submitted transaction differs from data originally sent to the wallet. */
onModification?: (response: VitalTxFields) => void | Generator<unknown, void, unknown> onModification?: (response: TransactionResponse) => void | Generator<unknown, void, unknown>
} }
export function* handleOnChainStep<T extends OnChainTransactionStep>(params: HandleOnChainStepParams<T>) { export function* handleOnChainStep<T extends OnChainTransactionStep>(params: HandleOnChainStepParams<T>) {
const { account, step, setCurrentStep, info, allowDuplicativeTx, ignoreInterrupt, onModification } = params const { account, step, setCurrentStep, info, allowDuplicativeTx, ignoreInterrupt, onModification } = params
const { chainId } = step.txRequest const { chainId } = step.txRequest
const signer = yield* call(getSigner, account.address)
addTransactionBreadcrumb({ step, data: { ...info } }) addTransactionBreadcrumb({ step, data: { ...info } })
...@@ -124,7 +121,8 @@ export function* handleOnChainStep<T extends OnChainTransactionStep>(params: Han ...@@ -124,7 +121,8 @@ export function* handleOnChainStep<T extends OnChainTransactionStep>(params: Han
setCurrentStep({ step, accepted: false }) setCurrentStep({ step, accepted: false })
// Prompt wallet to submit transaction // Prompt wallet to submit transaction
const { hash, nonce, data } = yield* call(submitTransaction, params) const response = yield* call([signer, 'sendTransaction'], step.txRequest)
const { hash, nonce, data } = response
// Trigger waiting UI after user accepts // Trigger waiting UI after user accepts
setCurrentStep({ step, accepted: true }) setCurrentStep({ step, accepted: true })
...@@ -133,7 +131,7 @@ export function* handleOnChainStep<T extends OnChainTransactionStep>(params: Han ...@@ -133,7 +131,7 @@ export function* handleOnChainStep<T extends OnChainTransactionStep>(params: Han
yield* put(addTransaction({ from: account.address, info, hash, nonce, chainId })) yield* put(addTransaction({ from: account.address, info, hash, nonce, chainId }))
if (step.txRequest.data !== data && onModification) { if (step.txRequest.data !== data && onModification) {
yield* call(onModification, { hash, data, nonce }) yield* call(onModification, response)
} }
// If the transaction flow was interrupted while awaiting input, throw an error after input is received // If the transaction flow was interrupted while awaiting input, throw an error after input is received
...@@ -169,58 +167,6 @@ function* handleOnChainConfirmation(params: HandleOnChainStepParams, hash: strin ...@@ -169,58 +167,6 @@ function* handleOnChainConfirmation(params: HandleOnChainStepParams, hash: strin
return hash return hash
} }
/** Submits a transaction and handles potential wallet errors */
function* submitTransaction(params: HandleOnChainStepParams): SagaGenerator<VitalTxFields> {
const { account, step } = params
const signer = yield* call(getSigner, account.address)
try {
const response = yield* call([signer, 'sendTransaction'], step.txRequest)
return transformTransactionResponse(response)
} catch (error) {
if (error && typeof error === 'object' && 'transactionHash' in error) {
return yield* recoverTransactionFromHash(error.transactionHash as `0x${string}`, step)
}
throw error
}
}
/** Polls for transaction details when only hash is known */
function* recoverTransactionFromHash(hash: `0x${string}`, step: OnChainTransactionStep): SagaGenerator<VitalTxFields> {
const transaction = yield* pollForTransaction(hash, step.txRequest.chainId)
if (!transaction) {
throw new TransactionStepFailedError({ message: `Transaction not found`, step })
}
return transformTransactionResponse(transaction)
}
/** Polls until transaction is found or timeout is reached */
function* pollForTransaction(hash: `0x${string}`, chainId: number) {
const POLL_INTERVAL = 2_000
const MAX_POLLING_TIME = isL2ChainId(chainId) ? 12_000 : 24_000
let elapsed = 0
while (elapsed < MAX_POLLING_TIME) {
try {
return yield* call(getTransaction, wagmiConfig, { chainId, hash })
} catch {
yield* delay(POLL_INTERVAL)
elapsed += POLL_INTERVAL
}
}
return null
}
/** Transforms a TransactionResponse or a Transaction into { hash: string; data: string; nonce: number } */
function transformTransactionResponse(response: TransactionResponse | Transaction): VitalTxFields {
if ('data' in response) {
return { hash: response.hash, data: response.data, nonce: response.nonce }
}
return { hash: response.hash, data: response.input, nonce: response.nonce }
}
interface HandleApprovalStepParams interface HandleApprovalStepParams
extends Omit<HandleOnChainStepParams<TokenApprovalTransactionStep | TokenRevocationTransactionStep>, 'info'> {} extends Omit<HandleOnChainStepParams<TokenApprovalTransactionStep | TokenRevocationTransactionStep>, 'info'> {}
export function* handleApprovalTransactionStep(params: HandleApprovalStepParams) { export function* handleApprovalTransactionStep(params: HandleApprovalStepParams) {
...@@ -229,14 +175,14 @@ export function* handleApprovalTransactionStep(params: HandleApprovalStepParams) ...@@ -229,14 +175,14 @@ export function* handleApprovalTransactionStep(params: HandleApprovalStepParams)
return yield* call(handleOnChainStep, { return yield* call(handleOnChainStep, {
...params, ...params,
info, info,
*onModification({ hash, data }: VitalTxFields) { *onModification(response: TransactionResponse) {
const { isInsufficient, approvedAmount } = checkApprovalAmount(data, step) const { isInsufficient, approvedAmount } = checkApprovalAmount(response, step)
// Update state to reflect hte actual approval amount submitted on-chain // Update state to reflect hte actual approval amount submitted on-chain
yield* put( yield* put(
updateTransactionInfo({ updateTransactionInfo({
chainId: step.txRequest.chainId, chainId: step.txRequest.chainId,
hash, hash: response.hash,
info: { ...info, amount: approvedAmount }, info: { ...info, amount: approvedAmount },
}), }),
) )
...@@ -259,9 +205,12 @@ function getApprovalTransactionInfo( ...@@ -259,9 +205,12 @@ function getApprovalTransactionInfo(
} }
} }
function checkApprovalAmount(data: string, step: TokenApprovalTransactionStep | TokenRevocationTransactionStep) { function checkApprovalAmount(
response: TransactionResponse,
step: TokenApprovalTransactionStep | TokenRevocationTransactionStep,
) {
const requiredAmount = BigInt(`0x${parseInt(step.amount, 10).toString(16)}`) const requiredAmount = BigInt(`0x${parseInt(step.amount, 10).toString(16)}`)
const submitted = parseERC20ApproveCalldata(data) const submitted = parseERC20ApproveCalldata(response.data)
const approvedAmount = submitted.amount.toString(10) const approvedAmount = submitted.amount.toString(10)
// Special case: for revoke tx's, the approval is insufficient if anything other than an empty approval was submitted on chain. // Special case: for revoke tx's, the approval is insufficient if anything other than an empty approval was submitted on chain.
......
...@@ -454,15 +454,13 @@ export function useInitialCurrencyState(): { ...@@ -454,15 +454,13 @@ export function useInitialCurrencyState(): {
defaultChainId, defaultChainId,
]) ])
const outputChainIsSupported = useSupportedChainId(parsedCurrencyState.outputChainId)
const initialOutputCurrencyAddress = useMemo( const initialOutputCurrencyAddress = useMemo(
() => () =>
// clear output if identical unless there's a supported outputChainId which means we're bridging // clear output if identical unless there's an outputChainId which means we're bridging
initialInputCurrencyAddress === parsedCurrencyState.outputCurrencyId && !outputChainIsSupported initialInputCurrencyAddress === parsedCurrencyState.outputCurrencyId && !parsedCurrencyState.outputChainId
? undefined ? undefined
: parsedCurrencyState.outputCurrencyId, : parsedCurrencyState.outputCurrencyId,
[initialInputCurrencyAddress, parsedCurrencyState.outputCurrencyId, outputChainIsSupported], [initialInputCurrencyAddress, parsedCurrencyState.outputCurrencyId, parsedCurrencyState.outputChainId],
) )
const initialInputCurrency = useCurrency(initialInputCurrencyAddress, initialChainId) const initialInputCurrency = useCurrency(initialInputCurrencyAddress, initialChainId)
......
import { TransactionResponse } from '@ethersproject/abstract-provider'
import { TradeType } from '@uniswap/sdk-core' import { TradeType } from '@uniswap/sdk-core'
import { VoteOption } from 'state/governance/types' import { VoteOption } from 'state/governance/types'
import { import {
...@@ -282,5 +281,3 @@ export interface ConfirmedTransactionDetails extends BaseTransactionDetails { ...@@ -282,5 +281,3 @@ export interface ConfirmedTransactionDetails extends BaseTransactionDetails {
} }
export type TransactionDetails = PendingTransactionDetails | ConfirmedTransactionDetails export type TransactionDetails = PendingTransactionDetails | ConfirmedTransactionDetails
export type VitalTxFields = Pick<TransactionResponse, 'hash' | 'nonce' | 'data'>
...@@ -194,7 +194,7 @@ export function useTrackedTokenPairs(): [Token, Token][] { ...@@ -194,7 +194,7 @@ export function useTrackedTokenPairs(): [Token, Token][] {
() => () =>
chainId && popularTokens?.topTokens chainId && popularTokens?.topTokens
? popularTokens.topTokens.flatMap((gqlToken) => { ? popularTokens.topTokens.flatMap((gqlToken) => {
if (!gqlToken || !gqlToken.address) { if (!gqlToken) {
return [] return []
} }
const token = gqlToCurrency(gqlToken) const token = gqlToCurrency(gqlToken)
......
...@@ -2,7 +2,6 @@ import { ParsedQs } from 'qs' ...@@ -2,7 +2,6 @@ import { ParsedQs } from 'qs'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/features/chains/chainInfo' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/features/chains/chainInfo'
import { useSupportedChainId } from 'uniswap/src/features/chains/hooks/useSupportedChainId'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
...@@ -30,9 +29,7 @@ export function useChainIdFromUrlParam(): UniverseChainId | undefined { ...@@ -30,9 +29,7 @@ export function useChainIdFromUrlParam(): UniverseChainId | undefined {
const chainName = useParams<{ chainName?: string }>().chainName const chainName = useParams<{ chainName?: string }>().chainName
// In the case where /explore/:chainName is used, the chainName is passed as a tab param // In the case where /explore/:chainName is used, the chainName is passed as a tab param
const tab = useParams<{ tab?: string }>().tab const tab = useParams<{ tab?: string }>().tab
const chainId = getChainIdFromChainUrlParam(chainName ?? tab) return getChainIdFromChainUrlParam(chainName ?? tab)
const supportedChainId = useSupportedChainId(chainId)
return supportedChainId
} }
export function getParsedChainId( export function getParsedChainId(
......
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow">
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M5.24408 14.7559C5.56951 15.0814 6.09715 15.0814 6.42259 14.7559L13.3333 7.84518V14.1667C13.3333 14.6269 13.7064 15 14.1667 15C14.6269 15 15 14.6269 15 14.1667V5.83333C15 5.3731 14.6269 5 14.1667 5H5.83333C5.3731 5 5 5.3731 5 5.83333C5 6.29357 5.3731 6.66667 5.83333 6.66667H12.1548L5.24408 13.5774C4.91864 13.9028 4.91864 14.4305 5.24408 14.7559Z" fill="#7D7D7D"/>
</g>
</svg>
...@@ -13,6 +13,7 @@ type InlineCardProps = { ...@@ -13,6 +13,7 @@ type InlineCardProps = {
iconBackgroundColor?: ColorTokens iconBackgroundColor?: ColorTokens
heading?: string | JSX.Element heading?: string | JSX.Element
CtaButtonIcon?: GeneratedIcon | ((props: IconProps) => JSX.Element) CtaButtonIcon?: GeneratedIcon | ((props: IconProps) => JSX.Element)
CtaButtonIconColor?: ColorTokens
onPressCtaButton?: () => void onPressCtaButton?: () => void
} }
...@@ -25,6 +26,7 @@ export function InlineCard({ ...@@ -25,6 +26,7 @@ export function InlineCard({
heading, heading,
description, description,
CtaButtonIcon, CtaButtonIcon,
CtaButtonIconColor = '$neutral3',
onPressCtaButton, onPressCtaButton,
}: InlineCardProps): JSX.Element { }: InlineCardProps): JSX.Element {
const icon = <Icon color={iconColor ?? color} size="$icon.20" /> const icon = <Icon color={iconColor ?? color} size="$icon.20" />
...@@ -64,7 +66,7 @@ export function InlineCard({ ...@@ -64,7 +66,7 @@ export function InlineCard({
</Flex> </Flex>
{CtaButtonIcon && ( {CtaButtonIcon && (
<TouchableArea onPress={onPressCtaButton}> <TouchableArea onPress={onPressCtaButton}>
<CtaButtonIcon color="$neutral3" size="$icon.20" /> <CtaButtonIcon color={CtaButtonIconColor} size="$icon.20" />
</TouchableArea> </TouchableArea>
)} )}
</Flex> </Flex>
......
import { G, Path, Svg } from 'react-native-svg'
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
import { createIcon } from '../factories/createIcon'
export const [ArrowUpRight, AnimatedArrowUpRight] = createIcon({
name: 'ArrowUpRight',
getIcon: (props) => (
<Svg viewBox="0 0 20 20" fill="none" {...props}>
<G id="arrow">
<Path
id="Vector"
d="M5.24408 14.7559C5.56951 15.0814 6.09715 15.0814 6.42259 14.7559L13.3333 7.84518V14.1667C13.3333 14.6269 13.7064 15 14.1667 15C14.6269 15 15 14.6269 15 14.1667V5.83333C15 5.3731 14.6269 5 14.1667 5H5.83333C5.3731 5 5 5.3731 5 5.83333C5 6.29357 5.3731 6.66667 5.83333 6.66667H12.1548L5.24408 13.5774C4.91864 13.9028 4.91864 14.4305 5.24408 14.7559Z"
fill={'currentColor' ?? '#7D7D7D'}
fillRule="evenodd"
clipRule="evenodd"
/>
</G>
</Svg>
),
defaultFill: '#7D7D7D',
})
...@@ -23,6 +23,7 @@ export * from './ArrowTurnDownRight' ...@@ -23,6 +23,7 @@ export * from './ArrowTurnDownRight'
export * from './ArrowUpCircle' export * from './ArrowUpCircle'
export * from './ArrowUpDown' export * from './ArrowUpDown'
export * from './ArrowUpInCircle' export * from './ArrowUpInCircle'
export * from './ArrowUpRight'
export * from './ArrowsLeftRight' export * from './ArrowsLeftRight'
export * from './BadgeDollar' export * from './BadgeDollar'
export * from './Bank' export * from './Bank'
......
import { ComponentProps, ReactNode } from 'react'
import { Popover } from 'tamagui'
import { WebBottomSheet } from 'ui/src/components/modal/AdaptiveWebModal'
type AdaptiveWebPopoverContentProps = Omit<ComponentProps<typeof Popover.Content>, 'children'> & {
children: ReactNode
isOpen: boolean
webBottomSheetProps?: Omit<ComponentProps<typeof WebBottomSheet>, 'children' | 'isOpen'>
}
/**
* AdaptiveWebPopoverContent is a responsive popover component that adapts to different screen sizes.
* On larger screens, it renders as a popover.
* On smaller screens (mobile devices), it adapts into a bottom sheet.
*/
export function AdaptiveWebPopoverContent({
children,
isOpen,
webBottomSheetProps,
...popoverContentProps
}: AdaptiveWebPopoverContentProps): JSX.Element {
return (
<>
<Popover.Content {...popoverContentProps}>{children}</Popover.Content>
<Popover.Adapt when="sm">
<WebBottomSheet isOpen={isOpen} {...(webBottomSheetProps || {})}>
<Popover.Adapt.Contents />
</WebBottomSheet>
</Popover.Adapt>
</>
)
}
...@@ -70,7 +70,6 @@ export type { GeneratedIcon, IconProps } from './components/factories/createIcon ...@@ -70,7 +70,6 @@ export type { GeneratedIcon, IconProps } from './components/factories/createIcon
export * from './components/input/utils' export * from './components/input/utils'
export { Flex, Inset, Separator, flexStyles, type FlexProps } from './components/layout' export { Flex, Inset, Separator, flexStyles, type FlexProps } from './components/layout'
export { ModalCloseIcon, WebBottomSheet } from './components/modal/AdaptiveWebModal' export { ModalCloseIcon, WebBottomSheet } from './components/modal/AdaptiveWebModal'
export { AdaptiveWebPopoverContent } from './components/popover/AdaptiveWebPopoverContent'
export * from './components/radio/Radio' export * from './components/radio/Radio'
export { ClickableWithinGesture } from './components/swipeablecards/ClickableWithinGesture' export { ClickableWithinGesture } from './components/swipeablecards/ClickableWithinGesture'
export { SwipeableCardStack } from './components/swipeablecards/SwipeableCardStack' export { SwipeableCardStack } from './components/swipeablecards/SwipeableCardStack'
......
import React from 'react' import React from 'react'
// eslint-disable-next-line no-restricted-imports
import type { ImageSourcePropType } from 'react-native'
import { Flex, FlexProps, Image, useSporeColors } from 'ui/src' import { Flex, FlexProps, Image, useSporeColors } from 'ui/src'
import { ALL_NETWORKS_LOGO, ALL_NETWORKS_LOGO_UNICHAIN } from 'ui/src/assets' import { ALL_NETWORKS_LOGO, ALL_NETWORKS_LOGO_UNICHAIN } from 'ui/src/assets'
import { iconSizes, zIndices } from 'ui/src/theme' import { iconSizes, zIndices } from 'ui/src/theme'
...@@ -8,7 +6,6 @@ import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' ...@@ -8,7 +6,6 @@ import { getChainInfo } from 'uniswap/src/features/chains/chainInfo'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { isMobileWeb } from 'utilities/src/platform'
export const SQUIRCLE_BORDER_RADIUS_RATIO = 0.3 export const SQUIRCLE_BORDER_RADIUS_RATIO = 0.3
...@@ -52,7 +49,7 @@ function _NetworkLogo({ ...@@ -52,7 +49,7 @@ function _NetworkLogo({
return ( return (
<Flex testID="all-networks-logo"> <Flex testID="all-networks-logo">
<NetworkImage logo={logo} imageSize={size} /> <Image resizeMode="contain" source={logo} style={imageStyle} />
</Flex> </Flex>
) )
} }
...@@ -62,19 +59,9 @@ function _NetworkLogo({ ...@@ -62,19 +59,9 @@ function _NetworkLogo({
return logo ? ( return logo ? (
<Flex testID="network-logo" overflow="hidden" style={imageStyle} zIndex={zIndices.mask}> <Flex testID="network-logo" overflow="hidden" style={imageStyle} zIndex={zIndices.mask}>
<NetworkImage logo={logo} imageSize={imageSize} /> <Image resizeMode="contain" source={logo} width={imageSize} height={imageSize} />
</Flex> </Flex>
) : null ) : null
} }
function NetworkImage({ logo, imageSize }: { logo: ImageSourcePropType; imageSize: number }): JSX.Element {
// As of iOS 18.3 network logos are no longer displaying because react-native-web-lite
// adds z-index: -1 to the image. This is a workaround to display the logos on mobile web.
return isMobileWeb && typeof logo === 'string' ? (
<img src={logo} style={{ width: imageSize, height: imageSize }} />
) : (
<Image resizeMode="contain" source={logo} width={imageSize} height={imageSize} />
)
}
export const NetworkLogo = React.memo(_NetworkLogo) export const NetworkLogo = React.memo(_NetworkLogo)
import { SharedEventName } from '@uniswap/analytics-events' import { SharedEventName } from '@uniswap/analytics-events'
import { useState } from 'react' import { useState } from 'react'
import { Flex, GeneratedIcon, InlineCard, LabeledCheckbox, Text } from 'ui/src' import { Flex, InlineCard, LabeledCheckbox, Text } from 'ui/src'
import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled' import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled'
import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types'
import { getWarningIcon, getWarningIconColors } from 'uniswap/src/components/warnings/utils' import { getWarningIcon, getWarningIconColors } from 'uniswap/src/components/warnings/utils'
...@@ -21,7 +21,6 @@ type InlineWarningCardProps = { ...@@ -21,7 +21,6 @@ type InlineWarningCardProps = {
headingTestId?: string headingTestId?: string
descriptionTestId?: string descriptionTestId?: string
analyticsProperties?: Record<string, unknown> analyticsProperties?: Record<string, unknown>
Icon?: GeneratedIcon
} }
export function InlineWarningCard({ export function InlineWarningCard({
...@@ -37,7 +36,6 @@ export function InlineWarningCard({ ...@@ -37,7 +36,6 @@ export function InlineWarningCard({
headingTestId, headingTestId,
descriptionTestId, descriptionTestId,
analyticsProperties, analyticsProperties,
Icon,
}: InlineWarningCardProps): JSX.Element | null { }: InlineWarningCardProps): JSX.Element | null {
const [checkedFallback, setCheckedFallback] = useState(false) const [checkedFallback, setCheckedFallback] = useState(false)
const { color, textColor, backgroundColor } = getWarningIconColors(severity) const { color, textColor, backgroundColor } = getWarningIconColors(severity)
...@@ -83,7 +81,7 @@ export function InlineWarningCard({ ...@@ -83,7 +81,7 @@ export function InlineWarningCard({
return ( return (
<InlineCard <InlineCard
CtaButtonIcon={shouldShowCtaIcon ? InfoCircleFilled : undefined} CtaButtonIcon={shouldShowCtaIcon ? InfoCircleFilled : undefined}
Icon={Icon ?? WarningIcon} Icon={WarningIcon}
color={textColor} color={textColor}
description={ description={
<Flex gap="$spacing8"> <Flex gap="$spacing8">
......
...@@ -12,12 +12,7 @@ import { ...@@ -12,12 +12,7 @@ import {
TokenSectionBaseList, TokenSectionBaseList,
TokenSectionBaseListRef, TokenSectionBaseListRef,
} from 'uniswap/src/components/TokenSelector/lists/TokenSectionBaseList/TokenSectionBaseList' } from 'uniswap/src/components/TokenSelector/lists/TokenSectionBaseList/TokenSectionBaseList'
import { import { OnSelectCurrency, TokenOption, TokenSection } from 'uniswap/src/components/TokenSelector/types'
OnSelectCurrency,
TokenOption,
TokenOptionSection,
TokenSection,
} from 'uniswap/src/components/TokenSelector/types'
import { useBottomSheetFocusHook } from 'uniswap/src/components/modals/hooks' import { useBottomSheetFocusHook } from 'uniswap/src/components/modals/hooks'
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
...@@ -181,7 +176,7 @@ function _TokenSelectorList({ ...@@ -181,7 +176,7 @@ function _TokenSelectorList({
isKeyboardOpen={isKeyboardOpen} isKeyboardOpen={isKeyboardOpen}
section={section} section={section}
showTokenAddress={showTokenAddress} showTokenAddress={showTokenAddress}
showWarnings={section.sectionKey === TokenOptionSection.BridgingTokens ? false : showTokenWarnings} showWarnings={showTokenWarnings}
tokenOption={item} tokenOption={item}
onSelectCurrency={onSelectCurrency} onSelectCurrency={onSelectCurrency}
/> />
......
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useState } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import Check from 'ui/src/assets/icons/check.svg' import Check from 'ui/src/assets/icons/check.svg'
...@@ -10,7 +10,7 @@ import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/type ...@@ -10,7 +10,7 @@ import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/type
import WarningIcon from 'uniswap/src/components/warnings/WarningIcon' import WarningIcon from 'uniswap/src/components/warnings/WarningIcon'
import { getWarningIconColors } from 'uniswap/src/components/warnings/utils' import { getWarningIconColors } from 'uniswap/src/components/warnings/utils'
import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { setHasSeenBridgingAnimation, setHasSeenBridgingTooltip } from 'uniswap/src/features/behaviorHistory/slice' import { setHasSeenBridgingTooltip } from 'uniswap/src/features/behaviorHistory/slice'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import TokenWarningModal from 'uniswap/src/features/tokens/TokenWarningModal' import TokenWarningModal from 'uniswap/src/features/tokens/TokenWarningModal'
import { getTokenWarningSeverity } from 'uniswap/src/features/tokens/safetyUtils' import { getTokenWarningSeverity } from 'uniswap/src/features/tokens/safetyUtils'
...@@ -19,7 +19,6 @@ import { getSymbolDisplayText } from 'uniswap/src/utils/currency' ...@@ -19,7 +19,6 @@ import { getSymbolDisplayText } from 'uniswap/src/utils/currency'
import { shortenAddress } from 'utilities/src/addresses' import { shortenAddress } from 'utilities/src/addresses'
import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard'
import { isInterface } from 'utilities/src/platform' import { isInterface } from 'utilities/src/platform'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
interface OptionProps { interface OptionProps {
option: TokenOption option: TokenOption
...@@ -61,9 +60,9 @@ function _TokenOptionItem({ ...@@ -61,9 +60,9 @@ function _TokenOptionItem({
const { colorSecondary: warningIconColor } = getWarningIconColors(severity) const { colorSecondary: warningIconColor } = getWarningIconColors(severity)
const shouldShowWarningModalOnPress = isBlocked || (severity !== WarningSeverity.None && !tokenWarningDismissed) const shouldShowWarningModalOnPress = isBlocked || (severity !== WarningSeverity.None && !tokenWarningDismissed)
const { shouldShowUnichainBridgingAnimation } = useUnichainTooltipVisibility() const { shouldShowUnichainBridgingTooltip } = useUnichainTooltipVisibility()
const isUnichainEth = currency.isNative && currency.chainId === UniverseChainId.Unichain const isUnichainEth = currency.isNative && currency.chainId === UniverseChainId.Unichain
const showUnichainPromoAnimation = shouldShowUnichainBridgingAnimation && isUnichainEth const showUnichainPromoAnimation = shouldShowUnichainBridgingTooltip && isUnichainEth
const handleShowWarningModal = useCallback((): void => { const handleShowWarningModal = useCallback((): void => {
dismissNativeKeyboard() dismissNativeKeyboard()
...@@ -93,17 +92,6 @@ function _TokenOptionItem({ ...@@ -93,17 +92,6 @@ function _TokenOptionItem({
onPress() onPress()
}, [onPress]) }, [onPress])
useEffect(() => {
if (showUnichainPromoAnimation) {
// delay to prevent ux jank
const delay = setTimeout(() => {
dispatch(setHasSeenBridgingAnimation(true))
}, ONE_SECOND_MS * 2)
return () => clearTimeout(delay)
}
return undefined
}, [dispatch, showUnichainPromoAnimation])
return ( return (
<> <>
<TouchableArea <TouchableArea
......
...@@ -37,12 +37,7 @@ export const SectionFooter = memo(function _SectionFooter({ ...@@ -37,12 +37,7 @@ export const SectionFooter = memo(function _SectionFooter({
} }
return ( return (
<AnimateInOrder <AnimateInOrder index={0} delayMs={800} animation="125msDelayedLong" enterStyle={{ opacity: 0 }}>
index={2}
delayMs={isMobileWeb ? 800 : 1500}
animation="125msDelayedLong"
enterStyle={{ opacity: 0 }}
>
<Flex <Flex
row row
alignItems="center" alignItems="center"
......
...@@ -104,10 +104,11 @@ export function NetworkOption({ ...@@ -104,10 +104,11 @@ export function NetworkOption({
<AnimateInOrder <AnimateInOrder
index={showUnichainAnimation ? 1 : 0} index={showUnichainAnimation ? 1 : 0}
delayMs={showUnichainAnimation ? (isInterface ? 250 : 750) : 0} delayMs={showUnichainAnimation ? (isInterface ? 250 : 750) : 0}
animation="125ms" animation={showUnichainAnimation ? '125ms' : null}
enterStyle={showUnichainAnimation ? { height: 0 } : null} enterStyle={showUnichainAnimation ? { height: 0, width: 200 } : null}
// fixed height is necessary to allow for smooth animation // defined height & width are necessary to allow for smooth animation
height={44} height={44}
width="auto"
> >
<Flex <Flex
row row
......
...@@ -41,6 +41,7 @@ export const uniswapUrls = { ...@@ -41,6 +41,7 @@ export const uniswapUrls = {
limitsInfo: `${helpUrl}/sections/24372644881293`, limitsInfo: `${helpUrl}/sections/24372644881293`,
limitsNetworkSupport: `${helpUrl}/articles/24470251716237-What-networks-do-limits-support`, limitsNetworkSupport: `${helpUrl}/articles/24470251716237-What-networks-do-limits-support`,
fiatOnRampHelp: `${helpUrl}/articles/11306574799117`, fiatOnRampHelp: `${helpUrl}/articles/11306574799117`,
fiatOffRampHelp: `${helpUrl}/articles/34006552258957`,
transferCryptoHelp: `${helpUrl}/articles/27103878635661-How-to-transfer-crypto-from-a-Robinhood-or-Coinbase-account-to-the-Uniswap-Wallet`, transferCryptoHelp: `${helpUrl}/articles/27103878635661-How-to-transfer-crypto-from-a-Robinhood-or-Coinbase-account-to-the-Uniswap-Wallet`,
moonpayRegionalAvailability: `${helpUrl}/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-`, moonpayRegionalAvailability: `${helpUrl}/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-`,
networkFeeInfo: `${helpUrl}/articles/8370337377805-What-is-a-network-fee-`, networkFeeInfo: `${helpUrl}/articles/8370337377805-What-is-a-network-fee-`,
......
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { FetchError } from 'uniswap/src/data/apiClients/FetchError' import { FetchError } from 'uniswap/src/data/apiClients/FetchError'
import { REQUEST_SOURCE, getVersionHeader } from 'uniswap/src/data/constants' import { REQUEST_SOURCE, getVersionHeader } from 'uniswap/src/data/constants'
import { logger } from 'utilities/src/logger/logger'
import { isMobileApp } from 'utilities/src/platform' import { isMobileApp } from 'utilities/src/platform'
export const BASE_UNISWAP_HEADERS = { export const BASE_UNISWAP_HEADERS = {
...@@ -31,14 +32,22 @@ export function createApiClient({ ...@@ -31,14 +32,22 @@ export function createApiClient({
return { return {
get fetch() { get fetch() {
return (path: string, options: Parameters<typeof fetch>[1]) => { return async (path: string, options: Parameters<typeof fetch>[1]) => {
return fetch(`${baseUrl}${path}`, { try {
...options, return await fetch(`${baseUrl}${path}`, {
headers: { ...options,
...headers, headers: {
...options?.headers, ...headers,
}, ...options?.headers,
}) },
})
} catch (error) {
logger.debug('apiClients', 'fetch', 'Failed to fetch', error, {
path,
...options,
})
throw error
}
} }
}, },
...@@ -63,6 +72,10 @@ export function createApiClient({ ...@@ -63,6 +72,10 @@ export function createApiClient({
if (!response.ok) { if (!response.ok) {
let data: object | undefined let data: object | undefined
logger.debug('apiClients', 'get', 'Failed to fetch', response, {
path,
...options,
})
try { try {
data = await response.json() data = await response.json()
} catch (e) { } catch (e) {
......
...@@ -38,6 +38,8 @@ import { ...@@ -38,6 +38,8 @@ import {
UniversalRouterVersion, UniversalRouterVersion,
} from 'uniswap/src/data/tradingApi/__generated__' } from 'uniswap/src/data/tradingApi/__generated__'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { getFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { isTestEnv } from 'utilities/src/environment/env' import { isTestEnv } from 'utilities/src/environment/env'
// TradingAPI team is looking into updating type generation to produce the following types for it's current QuoteResponse type: // TradingAPI team is looking into updating type generation to produce the following types for it's current QuoteResponse type:
...@@ -127,15 +129,18 @@ export async function fetchOrders({ orderIds }: { orderIds: string[] }): Promise ...@@ -127,15 +129,18 @@ export async function fetchOrders({ orderIds }: { orderIds: string[] }): Promise
} }
export async function fetchSwappableTokens(params: SwappableTokensParams): Promise<GetSwappableTokensResponse> { export async function fetchSwappableTokens(params: SwappableTokensParams): Promise<GetSwappableTokensResponse> {
const chainBlocklist = params.unichainEnabled ? [] : [UniverseChainId.Unichain.toString()] const unichainEnabled = getFeatureFlag(FeatureFlags.Unichain)
const chainBlocklist = unichainEnabled ? [] : [UniverseChainId.Unichain.toString()]
return await TradingApiClient.get<GetSwappableTokensResponse>(uniswapUrls.tradingApiPaths.swappableTokens, { return await TradingApiClient.get<GetSwappableTokensResponse>(uniswapUrls.tradingApiPaths.swappableTokens, {
params: { params: {
tokenIn: params.tokenIn, tokenIn: params.tokenIn,
tokenInChainId: params.tokenInChainId, tokenInChainId: params.tokenInChainId,
...(params.tokenOut && { tokenOut: params.tokenOut }),
...(params.tokenOutChainId && { tokenOutChainId: params.tokenOutChainId }),
}, },
headers: headers:
params.unichainEnabled || isTestEnv() unichainEnabled || isTestEnv()
? {} ? {}
: { : {
'x-chain-blocklist': chainBlocklist.join(','), 'x-chain-blocklist': chainBlocklist.join(','),
......
...@@ -13,17 +13,19 @@ import { TRADING_API_CACHE_KEY, fetchSwappableTokens } from 'uniswap/src/data/ap ...@@ -13,17 +13,19 @@ import { TRADING_API_CACHE_KEY, fetchSwappableTokens } from 'uniswap/src/data/ap
import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types'
import { ChainId, GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__' import { ChainId, GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__'
import { TradeableAsset } from 'uniswap/src/entities/assets' import { TradeableAsset } from 'uniswap/src/entities/assets'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { getFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { import {
getTokenAddressFromChainForTradingApi, getTokenAddressFromChainForTradingApi,
toTradingApiSupportedChainId, toTradingApiSupportedChainId,
} from 'uniswap/src/features/transactions/swap/utils/tradingApi' } from 'uniswap/src/features/transactions/swap/utils/tradingApi'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { MAX_REACT_QUERY_CACHE_TIME_MS } from 'utilities/src/time/time'
export type SwappableTokensParams = { export type SwappableTokensParams = {
tokenIn: Address tokenIn: Address
tokenInChainId: ChainId tokenInChainId: ChainId
unichainEnabled?: boolean tokenOut?: Address
tokenOutChainId?: ChainId
} }
export function useTradingApiSwappableTokensQuery({ export function useTradingApiSwappableTokensQuery({
...@@ -38,9 +40,6 @@ export function useTradingApiSwappableTokensQuery({ ...@@ -38,9 +40,6 @@ export function useTradingApiSwappableTokensQuery({
return useQuery<GetSwappableTokensResponse>({ return useQuery<GetSwappableTokensResponse>({
queryKey, queryKey,
queryFn: params ? swappableTokensQueryFn(params) : skipToken, queryFn: params ? swappableTokensQueryFn(params) : skipToken,
// In order for `getSwappableTokensQueryData` to be more likely to have cached data,
// we set the `gcTime` to the longest possible time.
gcTime: MAX_REACT_QUERY_CACHE_TIME_MS,
...rest, ...rest,
}) })
} }
...@@ -77,9 +76,6 @@ export function usePrefetchSwappableTokens(input: Maybe<TradeableAsset>): void { ...@@ -77,9 +76,6 @@ export function usePrefetchSwappableTokens(input: Maybe<TradeableAsset>): void {
tokenIn, tokenIn,
tokenInChainId, tokenInChainId,
}), }),
// In order for `getSwappableTokensQueryData` to be more likely to have cached data,
// we set the `gcTime` to the longest possible time.
gcTime: MAX_REACT_QUERY_CACHE_TIME_MS,
}) })
} }
...@@ -92,7 +88,14 @@ export function usePrefetchSwappableTokens(input: Maybe<TradeableAsset>): void { ...@@ -92,7 +88,14 @@ export function usePrefetchSwappableTokens(input: Maybe<TradeableAsset>): void {
} }
const swappableTokensQueryKey = (params?: SwappableTokensParams): QueryKey => { const swappableTokensQueryKey = (params?: SwappableTokensParams): QueryKey => {
return [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.swappableTokens, params] const unichainEnabled = getFeatureFlag(FeatureFlags.Unichain)
return [
TRADING_API_CACHE_KEY,
uniswapUrls.tradingApiPaths.swappableTokens,
params?.tokenIn,
params?.tokenInChainId,
unichainEnabled,
]
} }
const swappableTokensQueryFn = ( const swappableTokensQueryFn = (
......
...@@ -3,7 +3,7 @@ import { getNativeAddress } from 'uniswap/src/constants/addresses' ...@@ -3,7 +3,7 @@ import { getNativeAddress } from 'uniswap/src/constants/addresses'
import { GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__' import { GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__'
import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { buildCurrency } from 'uniswap/src/features/dataApi/utils' import { buildCurrency, getCurrencySafetyInfo } from 'uniswap/src/features/dataApi/utils'
import { NATIVE_ADDRESS_FOR_TRADING_API } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { NATIVE_ADDRESS_FOR_TRADING_API } from 'uniswap/src/features/transactions/swap/utils/tradingApi'
import { currencyId } from 'uniswap/src/utils/currencyId' import { currencyId } from 'uniswap/src/utils/currencyId'
...@@ -29,12 +29,15 @@ export function tradingApiSwappableTokenToCurrencyInfo( ...@@ -29,12 +29,15 @@ export function tradingApiSwappableTokenToCurrencyInfo(
return undefined return undefined
} }
const safetyLevel = toGqlSafetyLevel(token.project.safetyLevel)
const currencyInfo: CurrencyInfo = { const currencyInfo: CurrencyInfo = {
currency, currency,
currencyId: currencyId(currency), currencyId: currencyId(currency),
logoUrl: token.project?.logo?.url, logoUrl: token.project.logo?.url,
isSpam: token.project?.isSpam, isSpam: token.project.isSpam,
safetyLevel: toGqlSafetyLevel(token.project?.safetyLevel), safetyLevel,
safetyInfo: getCurrencySafetyInfo(safetyLevel ?? undefined),
} }
return currencyInfo return currencyInfo
......
...@@ -830,8 +830,9 @@ query SearchTokens($searchQuery: String!, $chains: [Chain!]!) { ...@@ -830,8 +830,9 @@ query SearchTokens($searchQuery: String!, $chains: [Chain!]!) {
query ExploreSearch( query ExploreSearch(
$searchQuery: String! $searchQuery: String!
$nftCollectionsFilter: NftCollectionsFilterInput! $nftCollectionsFilter: NftCollectionsFilterInput!
$chains: [Chain!]
) { ) {
searchTokens(searchQuery: $searchQuery) { searchTokens(searchQuery: $searchQuery, chains: $chains) {
chain chain
address address
decimals decimals
...@@ -1014,6 +1015,61 @@ fragment TopTokenParts on Token { ...@@ -1014,6 +1015,61 @@ fragment TopTokenParts on Token {
} }
} }
query ExploreTokensTab($topTokensOrderBy: TokenSortableField!, $chain: Chain!, $pageSize: Int!) {
topTokens(
chain: $chain
page: 1
pageSize: $pageSize
orderBy: $topTokensOrderBy
) {
...TopTokenParts
}
# `topTokens` returns WETH rather than ETH
# here we retrieve ETH information to swap out in the UI
eth: token(address: null, chain: $chain) {
...TopTokenParts
}
}
fragment AITopTokenParts on Token {
symbol
chain
address
market {
totalValueLocked {
value
}
volume(duration: DAY) {
value
}
}
name
project {
markets(currencies: [USD]) {
price {
value
}
pricePercentChange24h {
value
}
marketCap {
value
}
}
}
}
query AITopTokens($topTokensOrderBy: TokenSortableField!, $chain: Chain!, $pageSize: Int!) {
topTokens(
chain: $chain
page: 1
pageSize: $pageSize
orderBy: $topTokensOrderBy
) {
...AITopTokenParts
}
}
fragment HomeScreenTokenParts on Token { fragment HomeScreenTokenParts on Token {
...TokenBasicInfoParts ...TokenBasicInfoParts
project { project {
......
query SearchPopularTokensWeb($chain: Chain!, $orderBy: TokenSortableField) {
topTokens(chain: $chain, orderBy: $orderBy, page: 1, pageSize: 100) {
id
address
chain
symbol
name
decimals
project {
id
logoUrl
safetyLevel
isSpam
}
}
}
fragment TokenPrice on Token {
id
project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
market(currency: USD) {
id
price {
id
value
}
}
}
query V3Pool($chain: Chain!, $address: String!) { query V3Pool($chain: Chain!, $address: String!) {
v3Pool(chain: $chain, address: $address) { v3Pool(chain: $chain, address: $address) {
id id
...@@ -31,12 +6,38 @@ query V3Pool($chain: Chain!, $address: String!) { ...@@ -31,12 +6,38 @@ query V3Pool($chain: Chain!, $address: String!) {
feeTier feeTier
token0 { token0 {
...SimpleTokenDetails ...SimpleTokenDetails
...TokenPrice project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
} }
token0Supply token0Supply
token1 { token1 {
...SimpleTokenDetails ...SimpleTokenDetails
...TokenPrice project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
} }
token1Supply token1Supply
txCount txCount
...@@ -68,12 +69,38 @@ query V4Pool($chain: Chain!, $poolId: String!) { ...@@ -68,12 +69,38 @@ query V4Pool($chain: Chain!, $poolId: String!) {
} }
token0 { token0 {
...SimpleTokenDetails ...SimpleTokenDetails
...TokenPrice project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
} }
token0Supply token0Supply
token1 { token1 {
...SimpleTokenDetails ...SimpleTokenDetails
...TokenPrice project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
} }
token1Supply token1Supply
txCount txCount
...@@ -163,12 +190,38 @@ query V2Pair($chain: Chain!, $address: String!) { ...@@ -163,12 +190,38 @@ query V2Pair($chain: Chain!, $address: String!) {
address address
token0 { token0 {
...SimpleTokenDetails ...SimpleTokenDetails
...TokenPrice project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
} }
token0Supply token0Supply
token1 { token1 {
...SimpleTokenDetails ...SimpleTokenDetails
...TokenPrice project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
logo {
id
url
}
}
} }
token1Supply token1Supply
txCount txCount
......
query V4TokenTransactions($chain: Chain!, $address: String!, $first: Int!, $cursor: Int) {
token(chain: $chain, address: $address) {
...TransactionToken
v4Transactions(first: $first, timestampCursor: $cursor) {
...PoolTx
}
}
}
query V3TokenTransactions($chain: Chain!, $address: String!, $first: Int!, $cursor: Int) { query V3TokenTransactions($chain: Chain!, $address: String!, $first: Int!, $cursor: Int) {
token(chain: $chain, address: $address) { token(chain: $chain, address: $address) {
...TransactionToken ...TransactionToken
v3Transactions(first: $first, timestampCursor: $cursor) { v3Transactions(first: $first, timestampCursor: $cursor) {
...PoolTx timestamp
hash
account
token0 {
...TransactionToken
}
token0Quantity
token1 {
...TransactionToken
}
token1Quantity
usdValue {
value
}
type
} }
} }
} }
query V2TokenTransactions($chain: Chain!, $address: String!, $first: Int!, $cursor: Int) { query V2TokenTransactions($chain: Chain!, $address: String!, $first: Int!, $cursor: Int) {
token(chain: $chain, address: $address) { token(chain: $chain, address: $address) {
...TransactionToken ...TransactionToken
v2Transactions(first: $first, timestampCursor: $cursor) { v2Transactions(first: $first, timestampCursor: $cursor) {
...PoolTx timestamp
hash
account
token0 {
...TransactionToken
}
token0Quantity
token1 {
...TransactionToken
}
token1Quantity
usdValue {
value
}
type
} }
} }
} }
...@@ -20,37 +20,51 @@ fragment TransactionToken on Token { ...@@ -20,37 +20,51 @@ fragment TransactionToken on Token {
} }
} }
fragment PoolTx on PoolTransaction { query V3Transactions($chain: Chain!, $first: Int!, $cursor: Int) {
id v3Transactions(chain: $chain, first: $first, timestampCursor: $cursor) {
chain
protocolVersion
timestamp
hash
account
token0 {
...TransactionToken
}
token0Quantity
token1 {
...TransactionToken
}
token1Quantity
usdValue {
id id
value chain
protocolVersion
timestamp
hash
account
token0 {
...TransactionToken
}
token0Quantity
token1 {
...TransactionToken
}
token1Quantity
usdValue {
id
value
}
type
} }
type
}
query V4Transactions($chain: Chain!, $first: Int!, $cursor: Int) {
v4Transactions(chain: $chain, first: $first, timestampCursor: $cursor) { ...PoolTx }
}
query V3Transactions($chain: Chain!, $first: Int!, $cursor: Int) {
v3Transactions(chain: $chain, first: $first, timestampCursor: $cursor) { ...PoolTx }
} }
query V2Transactions($chain: Chain!, $first: Int!, $cursor: Int) { query V2Transactions($chain: Chain!, $first: Int!, $cursor: Int) {
v2Transactions(chain: $chain, first: $first, timestampCursor: $cursor) { ...PoolTx } v2Transactions(chain: $chain, first: $first, timestampCursor: $cursor) {
id
chain
protocolVersion
timestamp
hash
account
token0 {
...TransactionToken
}
token0Quantity
token1 {
...TransactionToken
}
token1Quantity
usdValue {
id
value
}
type
}
} }
...@@ -48,7 +48,7 @@ export const PERSONAL3_CONVERSION_URL = 'https://www.persona3.tech/events/attrib ...@@ -48,7 +48,7 @@ export const PERSONAL3_CONVERSION_URL = 'https://www.persona3.tech/events/attrib
const REDDIT_PIXEL_ID = 't2_tic7kuip' const REDDIT_PIXEL_ID = 't2_tic7kuip'
export const REDDIT_CONVERSION_URL = `https://ads-api.reddit.com/api/v2.0/conversions/events/${REDDIT_PIXEL_ID}` export const REDDIT_CONVERSION_URL = `https://ads-api.reddit.com/api/v2.0/conversions/events/${REDDIT_PIXEL_ID}`
const GOOGLE_CUSTOMER_ID = '3416874723' const GOOGLE_CUSTOMER_ID = '9871826344'
export const GOOGLE_CONVERSION_URL = `https://googleads.googleapis.com/v18/customers/${GOOGLE_CUSTOMER_ID}:uploadClickConversions` export const GOOGLE_CONVERSION_URL = `https://googleads.googleapis.com/v18/customers/${GOOGLE_CUSTOMER_ID}:uploadClickConversions`
export const GOOGLE_CONVERSION_DATETIME_FORMAT = 'yyyy-MM-dd HH:mm:ssXXX' export const GOOGLE_CONVERSION_DATETIME_FORMAT = 'yyyy-MM-dd HH:mm:ssXXX'
...@@ -56,24 +56,24 @@ export const GOOGLE_CONVERSION_EVENTS = { ...@@ -56,24 +56,24 @@ export const GOOGLE_CONVERSION_EVENTS = {
Web: { Web: {
WalletConnected: { WalletConnected: {
platformIdType: PlatformIdType.Google, platformIdType: PlatformIdType.Google,
eventId: `customers/${GOOGLE_CUSTOMER_ID}/conversionActions/7029146589`, eventId: 'customers/987-182-6344/conversionActions/6886211413',
eventName: 'Wallet Connected - Web - CAPI', eventName: 'Wallet Connected - Web - CAPI',
}, },
WalletFunded: { WalletFunded: {
platformIdType: PlatformIdType.Google, platformIdType: PlatformIdType.Google,
eventId: `customers/${GOOGLE_CUSTOMER_ID}/conversionActions/7029146586`, eventId: 'customers/987-182-6344/conversionActions/6886211407',
eventName: 'Wallet Funded - Web - CAPI', eventName: 'Wallet Funded - Web - CAPI',
}, },
}, },
Extension: { Extension: {
Download: { Download: {
platformIdType: PlatformIdType.Google, platformIdType: PlatformIdType.Google,
eventId: `customers/${GOOGLE_CUSTOMER_ID}/conversionActions/7029146592`, eventId: 'customers/987-182-6344/conversionActions/6886211416',
eventName: 'Download - Extension - CAPI', eventName: 'Download - Extension - CAPI',
}, },
WalletFunded: { WalletFunded: {
platformIdType: PlatformIdType.Google, platformIdType: PlatformIdType.Google,
eventId: `customers/${GOOGLE_CUSTOMER_ID}/conversionActions/7029146595`, eventId: 'customers/987-182-6344/conversionActions/6886211410',
eventName: 'Wallet Funded - Extension - CAPI', eventName: 'Wallet Funded - Extension - CAPI',
}, },
}, },
......
...@@ -24,8 +24,5 @@ export const selectHasSeenUnichainPromotionNetworkSelectorTooltip = (state: Unis ...@@ -24,8 +24,5 @@ export const selectHasSeenUnichainPromotionNetworkSelectorTooltip = (state: Unis
export const selectHasSeenUnichainPromotionBridgingTooltip = (state: UniswapState): boolean => export const selectHasSeenUnichainPromotionBridgingTooltip = (state: UniswapState): boolean =>
state.uniswapBehaviorHistory.unichainPromotion?.bridgingTooltipSeen === true state.uniswapBehaviorHistory.unichainPromotion?.bridgingTooltipSeen === true
export const selectHasSeenUnichainPromotionBridgingAnimation = (state: UniswapState): boolean =>
state.uniswapBehaviorHistory.unichainPromotion?.bridgingAnimationSeen === true
export const selectIsFirstUnichainBridgeSelection = (state: UniswapState): boolean => export const selectIsFirstUnichainBridgeSelection = (state: UniswapState): boolean =>
state.uniswapBehaviorHistory.unichainPromotion?.isFirstUnichainBridgeSelection === true state.uniswapBehaviorHistory.unichainPromotion?.isFirstUnichainBridgeSelection === true
...@@ -14,7 +14,6 @@ export interface UniswapBehaviorHistoryState { ...@@ -14,7 +14,6 @@ export interface UniswapBehaviorHistoryState {
networkSelectorAnimationSeen?: boolean networkSelectorAnimationSeen?: boolean
networkSelectorTooltipSeen?: boolean networkSelectorTooltipSeen?: boolean
bridgingTooltipSeen?: boolean bridgingTooltipSeen?: boolean
bridgingAnimationSeen?: boolean
isFirstUnichainBridgeSelection?: boolean isFirstUnichainBridgeSelection?: boolean
} }
} }
...@@ -29,7 +28,6 @@ export const initialUniswapBehaviorHistoryState: UniswapBehaviorHistoryState = { ...@@ -29,7 +28,6 @@ export const initialUniswapBehaviorHistoryState: UniswapBehaviorHistoryState = {
networkSelectorAnimationSeen: false, networkSelectorAnimationSeen: false,
networkSelectorTooltipSeen: false, networkSelectorTooltipSeen: false,
bridgingTooltipSeen: false, bridgingTooltipSeen: false,
bridgingAnimationSeen: false,
isFirstUnichainBridgeSelection: true, isFirstUnichainBridgeSelection: true,
}, },
} }
...@@ -71,10 +69,6 @@ const slice = createSlice({ ...@@ -71,10 +69,6 @@ const slice = createSlice({
state.unichainPromotion ??= {} state.unichainPromotion ??= {}
state.unichainPromotion.isFirstUnichainBridgeSelection = action.payload state.unichainPromotion.isFirstUnichainBridgeSelection = action.payload
}, },
setHasSeenBridgingAnimation: (state, action: PayloadAction<boolean>) => {
state.unichainPromotion ??= {}
state.unichainPromotion.bridgingAnimationSeen = action.payload
},
// Should only be used for testing // Should only be used for testing
resetUniswapBehaviorHistory: (_state, _action: PayloadAction) => { resetUniswapBehaviorHistory: (_state, _action: PayloadAction) => {
return initialUniswapBehaviorHistoryState return initialUniswapBehaviorHistoryState
...@@ -92,7 +86,6 @@ export const { ...@@ -92,7 +86,6 @@ export const {
setHasSeenNetworkSelectorTooltip, setHasSeenNetworkSelectorTooltip,
setHasSeenBridgingTooltip, setHasSeenBridgingTooltip,
setIsFirstUnichainBridgeSelection, setIsFirstUnichainBridgeSelection,
setHasSeenBridgingAnimation,
resetUniswapBehaviorHistory, resetUniswapBehaviorHistory,
} = slice.actions } = slice.actions
......
...@@ -3,8 +3,6 @@ import { useTradingApiSwappableTokensQuery } from 'uniswap/src/data/apiClients/t ...@@ -3,8 +3,6 @@ import { useTradingApiSwappableTokensQuery } from 'uniswap/src/data/apiClients/t
import { ChainId } from 'uniswap/src/data/tradingApi/__generated__' import { ChainId } from 'uniswap/src/data/tradingApi/__generated__'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { import {
NATIVE_ADDRESS_FOR_TRADING_API, NATIVE_ADDRESS_FOR_TRADING_API,
toTradingApiSupportedChainId, toTradingApiSupportedChainId,
...@@ -13,12 +11,10 @@ import { ...@@ -13,12 +11,10 @@ import {
const FALLBACK_NUM_CHAINS = 8 const FALLBACK_NUM_CHAINS = 8
export function useNumBridgingChains(): number { export function useNumBridgingChains(): number {
const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain)
const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({ const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({
params: { params: {
tokenIn: NATIVE_ADDRESS_FOR_TRADING_API, tokenIn: NATIVE_ADDRESS_FOR_TRADING_API,
tokenInChainId: ChainId._1, tokenInChainId: ChainId._1,
unichainEnabled,
}, },
}) })
...@@ -29,12 +25,10 @@ export function useNumBridgingChains(): number { ...@@ -29,12 +25,10 @@ export function useNumBridgingChains(): number {
} }
export function useIsBridgingChain(chainId: UniverseChainId): boolean { export function useIsBridgingChain(chainId: UniverseChainId): boolean {
const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain)
const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({ const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({
params: { params: {
tokenIn: NATIVE_ADDRESS_FOR_TRADING_API, tokenIn: NATIVE_ADDRESS_FOR_TRADING_API,
tokenInChainId: ChainId._1, tokenInChainId: ChainId._1,
unichainEnabled,
}, },
}) })
...@@ -45,12 +39,10 @@ export function useIsBridgingChain(chainId: UniverseChainId): boolean { ...@@ -45,12 +39,10 @@ export function useIsBridgingChain(chainId: UniverseChainId): boolean {
} }
export function useBridgingSupportedChainIds(): UniverseChainId[] { export function useBridgingSupportedChainIds(): UniverseChainId[] {
const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain)
const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({ const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({
params: { params: {
tokenIn: NATIVE_ADDRESS_FOR_TRADING_API, tokenIn: NATIVE_ADDRESS_FOR_TRADING_API,
tokenInChainId: ChainId._1, tokenInChainId: ChainId._1,
unichainEnabled,
}, },
}) })
......
...@@ -14,8 +14,6 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type ...@@ -14,8 +14,6 @@ import { ALL_CHAIN_IDS, UniverseChainId } from 'uniswap/src/features/chains/type
import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { CurrencyInfo, PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { CurrencyInfo, PortfolioBalance } from 'uniswap/src/features/dataApi/types'
import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { import {
NATIVE_ADDRESS_FOR_TRADING_API, NATIVE_ADDRESS_FOR_TRADING_API,
getTokenAddressFromChainForTradingApi, getTokenAddressFromChainForTradingApi,
...@@ -56,14 +54,12 @@ export function useBridgingTokenWithHighestBalance({ ...@@ -56,14 +54,12 @@ export function useBridgingTokenWithHighestBalance({
fetchPolicy: 'cache-first', fetchPolicy: 'cache-first',
}) })
const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain)
const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({ const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({
params: params:
otherChainBalances && otherChainBalances?.length > 0 && tokenIn && tokenInChainId otherChainBalances && otherChainBalances?.length > 0 && tokenIn && tokenInChainId
? { ? {
tokenIn, tokenIn,
tokenInChainId, tokenInChainId,
unichainEnabled,
} }
: undefined, : undefined,
}) })
...@@ -122,7 +118,7 @@ export function useBridgingTokensOptions({ ...@@ -122,7 +118,7 @@ export function useBridgingTokensOptions({
}): GqlResult<TokenOption[] | undefined> & { shouldNest?: boolean } { }): GqlResult<TokenOption[] | undefined> & { shouldNest?: boolean } {
const tokenIn = input?.address ? getTokenAddressFromChainForTradingApi(input.address, input.chainId) : undefined const tokenIn = input?.address ? getTokenAddressFromChainForTradingApi(input.address, input.chainId) : undefined
const tokenInChainId = toTradingApiSupportedChainId(input?.chainId) const tokenInChainId = toTradingApiSupportedChainId(input?.chainId)
const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain)
const { const {
data: bridgingTokens, data: bridgingTokens,
isLoading: loadingBridgingTokens, isLoading: loadingBridgingTokens,
...@@ -134,7 +130,6 @@ export function useBridgingTokensOptions({ ...@@ -134,7 +130,6 @@ export function useBridgingTokensOptions({
? { ? {
tokenIn, tokenIn,
tokenInChainId, tokenInChainId,
unichainEnabled,
} }
: undefined, : undefined,
}) })
......
...@@ -827,7 +827,7 @@ export const UNIVERSE_CHAIN_INFO: Record<UniverseChainId, UniverseChainInfo> = { ...@@ -827,7 +827,7 @@ export const UNIVERSE_CHAIN_INFO: Record<UniverseChainId, UniverseChainInfo> = {
pendingTransactionsRetryOptions: undefined, pendingTransactionsRetryOptions: undefined,
rpcUrls: { rpcUrls: {
[RPCType.Public]: { http: [getQuicknodeEndpointUrl(UniverseChainId.Unichain)] }, [RPCType.Public]: { http: [getQuicknodeEndpointUrl(UniverseChainId.Unichain)] },
[RPCType.Default]: { http: ['https://mainnet.unichain.org'] }, [RPCType.Default]: { http: [getQuicknodeEndpointUrl(UniverseChainId.Unichain)] },
[RPCType.Interface]: { http: [getQuicknodeEndpointUrl(UniverseChainId.Unichain)] }, [RPCType.Interface]: { http: [getQuicknodeEndpointUrl(UniverseChainId.Unichain)] },
}, },
spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_UNICHAIN, 10_000e6), spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_UNICHAIN, 10_000e6),
......
...@@ -30,9 +30,11 @@ export enum FeatureFlags { ...@@ -30,9 +30,11 @@ export enum FeatureFlags {
ExtensionClaimUnitag, ExtensionClaimUnitag,
ExtensionPromotionGA, ExtensionPromotionGA,
FiatOffRamp, FiatOffRamp,
NotificationPriceAlerts, NotificationPriceAlertsAndroid,
NotificationPriceAlertsIOS,
NotificationOnboardingCard, NotificationOnboardingCard,
NotificationUnfundedWallets, NotificationUnfundedWalletsAndroid,
NotificationUnfundedWalletsIOS,
OnboardingKeyring, OnboardingKeyring,
OpenAIAssistant, OpenAIAssistant,
PrivateRpc, PrivateRpc,
...@@ -125,8 +127,10 @@ export const WALLET_FEATURE_FLAG_NAMES = new Map<FeatureFlags, string>([ ...@@ -125,8 +127,10 @@ export const WALLET_FEATURE_FLAG_NAMES = new Map<FeatureFlags, string>([
[FeatureFlags.FiatOffRamp, 'fiat-offramp'], [FeatureFlags.FiatOffRamp, 'fiat-offramp'],
[FeatureFlags.ForAggregator, 'for-aggregator'], [FeatureFlags.ForAggregator, 'for-aggregator'],
[FeatureFlags.NotificationOnboardingCard, 'notification_onboarding_card'], [FeatureFlags.NotificationOnboardingCard, 'notification_onboarding_card'],
[FeatureFlags.NotificationPriceAlerts, 'notification_price_alerts'], [FeatureFlags.NotificationPriceAlertsAndroid, 'notification_price_alerts_android'],
[FeatureFlags.NotificationUnfundedWallets, 'notification_unfunded_wallet'], [FeatureFlags.NotificationPriceAlertsIOS, 'notification_price_alerts_ios'],
[FeatureFlags.NotificationUnfundedWalletsAndroid, 'notification_unfunded_wallet_android'],
[FeatureFlags.NotificationUnfundedWalletsIOS, 'notification_unfunded_wallet_ios'],
[FeatureFlags.OnboardingKeyring, 'onboarding-keyring'], [FeatureFlags.OnboardingKeyring, 'onboarding-keyring'],
[FeatureFlags.OpenAIAssistant, 'openai-assistant'], [FeatureFlags.OpenAIAssistant, 'openai-assistant'],
[FeatureFlags.PrivateRpc, 'mev-blocker'], [FeatureFlags.PrivateRpc, 'mev-blocker'],
......
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
TokenDocument, TokenDocument,
TokenQuery, TokenQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { IndicativeQuoteResponse, TradeType } from 'uniswap/src/data/tradingApi/__generated__' import { IndicativeQuoteRequest, IndicativeQuoteResponse, TradeType } from 'uniswap/src/data/tradingApi/__generated__'
import { UniverseChainId } from 'uniswap/src/features/chains/types' import { UniverseChainId } from 'uniswap/src/features/chains/types'
import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils'
import { currencyIdToContractInput, gqlTokenToCurrencyInfo } from 'uniswap/src/features/dataApi/utils' import { currencyIdToContractInput, gqlTokenToCurrencyInfo } from 'uniswap/src/features/dataApi/utils'
...@@ -183,31 +183,14 @@ async function getDenominatedValue({ ...@@ -183,31 +183,14 @@ async function getDenominatedValue({
return undefined return undefined
} }
const indicativeQuoteParams = { const indicativeQuote = await fetchIndicativeQuote({
type: TradeType.EXACT_INPUT, type: TradeType.EXACT_INPUT,
amount: onchainQuantityCurrencyAmount.quotient.toString(), amount: onchainQuantityCurrencyAmount.quotient.toString(),
tokenInChainId: chainId, tokenInChainId: chainId,
tokenOutChainId: chainId, tokenOutChainId: chainId,
tokenIn: tokenAddress, tokenIn: tokenAddress,
tokenOut: stablecoinCurrency.address, tokenOut: stablecoinCurrency.address,
} })
let indicativeQuote: IndicativeQuoteResponse | undefined
try {
indicativeQuote = await fetchTradingApiIndicativeQuoteIgnoring404({ params: indicativeQuoteParams })
} catch (error) {
// We log any other errors, but we don't want to throw and instead just continue with an "N/A" value.
logger.error(error, {
tags: {
file: 'fetchOnChainBalances.ts',
function: 'getDenominatedValue',
},
extra: {
indicativeQuoteParams,
},
})
}
const amountOut = indicativeQuote?.output.amount const amountOut = indicativeQuote?.output.amount
...@@ -255,3 +238,21 @@ function getInferredCachedDenominatedValue({ ...@@ -255,3 +238,21 @@ function getInferredCachedDenominatedValue({
return undefined return undefined
} }
async function fetchIndicativeQuote(params: IndicativeQuoteRequest): Promise<IndicativeQuoteResponse | undefined> {
try {
return await fetchTradingApiIndicativeQuoteIgnoring404({ params })
} catch (error) {
// We log any other errors, but we don't want to throw and instead just continue with an "N/A" value.
logger.error(error, {
tags: {
file: 'fetchOnChainBalances.ts',
function: 'getIndicativeQuote',
},
extra: {
params,
},
})
return undefined
}
}
...@@ -358,6 +358,11 @@ export type LiquidityAnalyticsProperties = ITraceContext & { ...@@ -358,6 +358,11 @@ export type LiquidityAnalyticsProperties = ITraceContext & {
currencyInfo1Decimals: number currencyInfo1Decimals: number
} }
export type NotificationToggleLoggingType =
| 'settings_general_updates_enabled'
| 'settings_price_alerts_enabled'
| 'wallet_activity'
// Please sort new values by EventName type! // Please sort new values by EventName type!
export type UniverseEventProperties = { export type UniverseEventProperties = {
[ExtensionEventName.OnboardingLoad]: undefined [ExtensionEventName.OnboardingLoad]: undefined
...@@ -590,6 +595,7 @@ export type UniverseEventProperties = { ...@@ -590,6 +595,7 @@ export type UniverseEventProperties = {
[MobileEventName.FiatOnRampQuickActionButtonPressed]: ITraceContext [MobileEventName.FiatOnRampQuickActionButtonPressed]: ITraceContext
[MobileEventName.NotificationsToggled]: ITraceContext & { [MobileEventName.NotificationsToggled]: ITraceContext & {
enabled: boolean enabled: boolean
type: NotificationToggleLoggingType
} }
[MobileEventName.OnboardingCompleted]: OnboardingCompletedProps & ITraceContext [MobileEventName.OnboardingCompleted]: OnboardingCompletedProps & ITraceContext
[MobileEventName.PerformanceReport]: RenderPassReport [MobileEventName.PerformanceReport]: RenderPassReport
......
...@@ -159,7 +159,7 @@ export function TransactionDetails({ ...@@ -159,7 +159,7 @@ export function TransactionDetails({
{showWarning && warning && onShowWarning && ( {showWarning && warning && onShowWarning && (
<TransactionWarning warning={warning} onShowWarning={onShowWarning} /> <TransactionWarning warning={warning} onShowWarning={onShowWarning} />
)} )}
{!isInterface && isSwap && ( {isSwap && (
<TransactionSettingsModal <TransactionSettingsModal
settings={[SlippageUpdate]} settings={[SlippageUpdate]}
initialSelectedSetting={SlippageUpdate} initialSelectedSetting={SlippageUpdate}
......
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { DEFAULT_CUSTOM_DEADLINE } from 'uniswap/src/constants/transactions'
import { import {
DEFAULT_PROTOCOL_OPTIONS, DEFAULT_PROTOCOL_OPTIONS,
FrontendSupportedProtocol, FrontendSupportedProtocol,
...@@ -22,6 +23,7 @@ export interface TransactionSettingsState { ...@@ -22,6 +23,7 @@ export interface TransactionSettingsState {
} }
export const initialTransactionSettingsState: TransactionSettingsState = { export const initialTransactionSettingsState: TransactionSettingsState = {
customDeadline: DEFAULT_CUSTOM_DEADLINE,
selectedProtocols: DEFAULT_PROTOCOL_OPTIONS, selectedProtocols: DEFAULT_PROTOCOL_OPTIONS,
isOnlyV2Allowed: false, isOnlyV2Allowed: false,
slippageWarningModalSeen: false, slippageWarningModalSeen: false,
......
...@@ -13,13 +13,13 @@ import { TransactionSettingsContextState } from 'uniswap/src/features/transactio ...@@ -13,13 +13,13 @@ import { TransactionSettingsContextState } from 'uniswap/src/features/transactio
import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo'
import { Trade } from 'uniswap/src/features/transactions/swap/types/trade' import { Trade } from 'uniswap/src/features/transactions/swap/types/trade'
import { SwapEventType, timestampTracker } from 'uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker' import { SwapEventType, timestampTracker } from 'uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker'
import { slippageToleranceToPercent } from 'uniswap/src/features/transactions/swap/utils/format'
import { getSwapFeeUsd } from 'uniswap/src/features/transactions/swap/utils/getSwapFeeUsd' import { getSwapFeeUsd } from 'uniswap/src/features/transactions/swap/utils/getSwapFeeUsd'
import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing' import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing'
import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi'
import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
import { getCurrencyAddressForAnalytics } from 'uniswap/src/utils/currencyId' import { getCurrencyAddressForAnalytics } from 'uniswap/src/utils/currencyId'
import { percentFromFloat } from 'utilities/src/format/percent'
import { NumberType } from 'utilities/src/format/types' import { NumberType } from 'utilities/src/format/types'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { ITraceContext, useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { ITraceContext, useTrace } from 'utilities/src/telemetry/trace/TraceContext'
...@@ -91,7 +91,7 @@ export function getBaseTradeAnalyticsProperties({ ...@@ -91,7 +91,7 @@ export function getBaseTradeAnalyticsProperties({
const finalOutputAmount = feeCurrencyAmount ? trade.outputAmount.subtract(feeCurrencyAmount) : trade.outputAmount const finalOutputAmount = feeCurrencyAmount ? trade.outputAmount.subtract(feeCurrencyAmount) : trade.outputAmount
const slippagePercent = slippageToleranceToPercent(trade.slippageTolerance ?? 0) const slippagePercent = percentFromFloat(trade.slippageTolerance ?? 0)
return { return {
...trace, ...trace,
......
...@@ -77,9 +77,6 @@ export function SwapFormSettings({ ...@@ -77,9 +77,6 @@ export function SwapFormSettings({
const topAlignment = adjustTopAlignment ? (isInterface ? -38 : 6) : 0 const topAlignment = adjustTopAlignment ? (isInterface ? -38 : 6) : 0
const rightAlignment = adjustRightAlignment ? (isMobileApp ? 24 : 4) : 0 const rightAlignment = adjustRightAlignment ? (isMobileApp ? 24 : 4) : 0
const popoverOffset = isInterface
? { crossAxis: adjustRightAlignment ? 0 : 8, mainAxis: adjustTopAlignment ? 0 : 8 }
: undefined
const showCustomSlippage = customSlippageTolerance && !isBridgeTrade const showCustomSlippage = customSlippageTolerance && !isBridgeTrade
...@@ -113,7 +110,6 @@ export function SwapFormSettings({ ...@@ -113,7 +110,6 @@ export function SwapFormSettings({
{!isViewOnlyWallet && ( {!isViewOnlyWallet && (
<Popover <Popover
offset={popoverOffset}
placement="bottom-end" placement="bottom-end"
open={showTransactionSettingsModal} open={showTransactionSettingsModal}
onOpenChange={(open: boolean) => { onOpenChange={(open: boolean) => {
......
...@@ -14,7 +14,7 @@ import { Warning, WarningLabel } from 'uniswap/src/components/modals/WarningModa ...@@ -14,7 +14,7 @@ import { Warning, WarningLabel } from 'uniswap/src/components/modals/WarningModa
import { getCanonicalBridgingDappUrls } from 'uniswap/src/features/bridging/constants' import { getCanonicalBridgingDappUrls } from 'uniswap/src/features/bridging/constants'
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 { getChainLabel } from 'uniswap/src/features/chains/utils' import { getChainLabel, toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { import {
useFormattedUniswapXGasFeeInfo, useFormattedUniswapXGasFeeInfo,
useGasFeeFormattedDisplayAmounts, useGasFeeFormattedDisplayAmounts,
...@@ -27,7 +27,7 @@ import { useSwapTxContext } from 'uniswap/src/features/transactions/swap/context ...@@ -27,7 +27,7 @@ import { useSwapTxContext } from 'uniswap/src/features/transactions/swap/context
import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning' import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning'
import { SwapRateRatio } from 'uniswap/src/features/transactions/swap/review/SwapRateRatio' import { SwapRateRatio } from 'uniswap/src/features/transactions/swap/review/SwapRateRatio'
import { IndicativeTrade, Trade } from 'uniswap/src/features/transactions/swap/types/trade' import { IndicativeTrade, Trade } from 'uniswap/src/features/transactions/swap/types/trade'
import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { isBridge, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing'
import { CurrencyField } from 'uniswap/src/types/currency' import { CurrencyField } from 'uniswap/src/types/currency'
import { useNetworkColors } from 'uniswap/src/utils/colors' import { useNetworkColors } from 'uniswap/src/utils/colors'
import { openUri } from 'uniswap/src/utils/linking' import { openUri } from 'uniswap/src/utils/linking'
...@@ -173,10 +173,6 @@ export function TradeInfoRow({ ...@@ -173,10 +173,6 @@ export function TradeInfoRow({
const warningColor = getAlertColor(warning?.severity) const warningColor = getAlertColor(warning?.severity)
const { isTestnetModeEnabled } = useEnabledChains() const { isTestnetModeEnabled } = useEnabledChains()
const {
derivedSwapInfo: { currencies },
} = useSwapFormContext()
if (isTestnetModeEnabled) { if (isTestnetModeEnabled) {
return null return null
} }
...@@ -186,11 +182,12 @@ export function TradeInfoRow({ ...@@ -186,11 +182,12 @@ export function TradeInfoRow({
} }
// On interface, if the warning is a no quotes found warning, we want to show an external link to a canonical bridge // On interface, if the warning is a no quotes found warning, we want to show an external link to a canonical bridge
const inputChainId = currencies.input?.currency.chainId
const outputChainId = currencies.output?.currency.chainId
const showCanonicalBridge = const showCanonicalBridge =
isInterface && warning?.type === WarningLabel.NoQuotesFound && inputChainId !== outputChainId isInterface &&
warning?.type === WarningLabel.NoQuotesFound &&
!debouncedTrade?.indicative &&
debouncedTrade &&
isBridge(debouncedTrade)
return ( return (
<Flex centered row> <Flex centered row>
...@@ -212,7 +209,9 @@ export function TradeInfoRow({ ...@@ -212,7 +209,9 @@ export function TradeInfoRow({
</Flex> </Flex>
{showCanonicalBridge ? ( {showCanonicalBridge ? (
<CanonicalBridgeLink chainId={outputChainId ?? UniverseChainId.Mainnet} /> <CanonicalBridgeLink
chainId={toSupportedChainId(debouncedTrade?.quote?.quote?.destinationChainId) ?? UniverseChainId.Mainnet}
/>
) : debouncedTrade ? ( ) : debouncedTrade ? (
<Accordion.Trigger <Accordion.Trigger
p="$none" p="$none"
......
...@@ -266,10 +266,7 @@ export function useParsedSwapWarnings(): ParsedWarnings { ...@@ -266,10 +266,7 @@ export function useParsedSwapWarnings(): ParsedWarnings {
} }
function getSwapWarningFromError(error: Error, t: TFunction, derivedSwapInfo: DerivedSwapInfo): Warning { function getSwapWarningFromError(error: Error, t: TFunction, derivedSwapInfo: DerivedSwapInfo): Warning {
// Trade object is null for quote not found case const isBridgeTrade = derivedSwapInfo.trade.trade !== null && isBridge(derivedSwapInfo.trade.trade)
const isBridgeTrade =
derivedSwapInfo.currencies.input?.currency.chainId !== derivedSwapInfo.currencies.output?.currency.chainId
if (error instanceof FetchError) { if (error instanceof FetchError) {
// Special case: rate limit errors are not parsed by errorCode // Special case: rate limit errors are not parsed by errorCode
if (isRateLimitFetchError(error)) { if (isRateLimitFetchError(error)) {
...@@ -294,16 +291,18 @@ function getSwapWarningFromError(error: Error, t: TFunction, derivedSwapInfo: De ...@@ -294,16 +291,18 @@ function getSwapWarningFromError(error: Error, t: TFunction, derivedSwapInfo: De
} }
} }
case Err404.errorCode.RESOURCE_NOT_FOUND: { // no bridging quotes found warning
if (isBridgeTrade) { case Err404.errorCode.RESOURCE_NOT_FOUND && isBridgeTrade: {
return { return {
type: WarningLabel.NoQuotesFound, type: WarningLabel.NoQuotesFound,
severity: WarningSeverity.Low, severity: WarningSeverity.Low,
action: WarningAction.DisableReview, action: WarningAction.DisableReview,
title: t('swap.warning.noQuotesFound.title'), title: t('swap.warning.noQuotesFound.title'),
message: t('swap.warning.noQuotesFound.bridging.message'), message: t('swap.warning.noQuotesFound.bridging.message'),
}
} }
}
case Err404.errorCode.RESOURCE_NOT_FOUND: {
return { return {
type: WarningLabel.NoRoutesError, type: WarningLabel.NoRoutesError,
severity: WarningSeverity.Low, severity: WarningSeverity.Low,
......
...@@ -77,7 +77,6 @@ export function SlippageControl({ saveOnBlur }: SlippageControlProps): JSX.Eleme ...@@ -77,7 +77,6 @@ export function SlippageControl({ saveOnBlur }: SlippageControlProps): JSX.Eleme
<Flex style={{ position: 'relative' }}> <Flex style={{ position: 'relative' }}>
<Input <Input
ref={inputRef} ref={inputRef}
keyboardType="decimal-pad"
backgroundColor={backgroundColor} backgroundColor={backgroundColor}
color={inputValueTextColor} color={inputValueTextColor}
editable={true} editable={true}
......
import type { TFunction } from 'i18next' import { useState } from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import { DeprecatedButton, Flex, Popover, Text, TouchableArea, isWeb, useSporeColors } from 'ui/src'
AdaptiveWebPopoverContent,
DeprecatedButton,
Flex,
isWeb,
Text,
TouchableArea,
useMedia,
useSporeColors,
} from 'ui/src'
import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron'
import { iconSizes } from 'ui/src/theme' import { iconSizes } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { WarningMessage } from 'uniswap/src/components/WarningMessage/WarningMessage' import { WarningMessage } from 'uniswap/src/components/WarningMessage/WarningMessage'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { SLIPPAGE_CRITICAL_TOLERANCE, WARNING_DEADLINE_TOLERANCE } from 'uniswap/src/constants/transactions' import { SLIPPAGE_CRITICAL_TOLERANCE, WARNING_DEADLINE_TOLERANCE } from 'uniswap/src/constants/transactions'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { import {
...@@ -22,10 +12,10 @@ import { ...@@ -22,10 +12,10 @@ import {
useTransactionSettingsContext, useTransactionSettingsContext,
} from 'uniswap/src/features/transactions/settings/contexts/TransactionSettingsContext' } from 'uniswap/src/features/transactions/settings/contexts/TransactionSettingsContext'
import { SwapFormContext, useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { SwapFormContext, useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext'
import { SwapSettingConfig, SwapSettingId } from 'uniswap/src/features/transactions/swap/settings/configs/types'
import { SwapSettingRow } from 'uniswap/src/features/transactions/swap/settings/SwapSettingsRow' import { SwapSettingRow } from 'uniswap/src/features/transactions/swap/settings/SwapSettingsRow'
import { SwapSettingConfig, SwapSettingId } from 'uniswap/src/features/transactions/swap/settings/configs/types'
import { useSlippageSettings } from 'uniswap/src/features/transactions/swap/settings/useSlippageSettings' import { useSlippageSettings } from 'uniswap/src/features/transactions/swap/settings/useSlippageSettings'
import { isExtension, isInterface, isMobileApp, isMobileWeb } from 'utilities/src/platform' import { isExtension, isInterface, isMobileWeb } from 'utilities/src/platform'
const POPOVER_WIDTH = 320 const POPOVER_WIDTH = 320
...@@ -44,29 +34,58 @@ const TransactionSettingsModalContent = ({ ...@@ -44,29 +34,58 @@ const TransactionSettingsModalContent = ({
onClose, onClose,
}: Omit<TransactionSettingsModalProps, 'isOpen'>): JSX.Element => { }: Omit<TransactionSettingsModalProps, 'isOpen'>): JSX.Element => {
const { t } = useTranslation() const { t } = useTranslation()
const media = useMedia() const { customSlippageTolerance, customDeadline } = useTransactionSettingsContext()
const { autoSlippageTolerance } = useSlippageSettings()
const isCriticalSlippage = customSlippageTolerance && customSlippageTolerance >= SLIPPAGE_CRITICAL_TOLERANCE
const [SelectedSetting, setSelectedSetting] = useState<SwapSettingConfig>()
const [SelectedSetting, setSelectedSetting] = useState<SwapSettingConfig | undefined>(initialSelectedSetting) const rowWarningContent: Record<SwapSettingId, { condition: boolean; render: () => JSX.Element | undefined }> = {
[SwapSettingId.SLIPPAGE]: {
condition: !!customSlippageTolerance && customSlippageTolerance > autoSlippageTolerance,
render: () => (
<WarningMessage
warningMessage={isCriticalSlippage ? t('swap.settings.slippage.warning') : t('swap.settings.slippage.alert')}
tooltipText={isWeb && !isMobileWeb ? t('swap.settings.slippage.warning.hover') : undefined}
color={isCriticalSlippage ? '$statusCritical' : '$statusWarning'}
/>
),
},
[SwapSettingId.DEADLINE]: {
condition: !!customDeadline && customDeadline >= WARNING_DEADLINE_TOLERANCE,
render: () => <WarningMessage warningMessage={t('swap.settings.deadline.warning')} color="$statusWarning" />,
},
}
const getSettingsRowWarning = (settingId: SwapSettingId): JSX.Element | undefined => {
const warning = rowWarningContent[settingId]
return warning?.condition ? warning.render() : undefined
}
const title = SelectedSetting ? SelectedSetting.renderTitle(t) : defaultTitle ?? t('swap.settings.title') const title = SelectedSetting ? SelectedSetting.renderTitle(t) : defaultTitle ?? t('swap.settings.title')
const screen = SelectedSetting?.Screen ? ( const screen = SelectedSetting?.Screen ? (
<SelectedSetting.Screen /> <SelectedSetting.Screen />
) : ( ) : (
<TopLevelSettings settings={settings} setSelectedSetting={setSelectedSetting} /> <Flex gap="$spacing8" py="$spacing12">
{settings.map((setting, index) => {
const warning = setting.settingId ? getSettingsRowWarning(setting.settingId) : undefined
return (
<SwapSettingRow
key={`swap-setting-${index}`}
setSelectedSetting={setSelectedSetting}
setting={setting}
warning={warning}
/>
)
})}
</Flex>
) )
// For selected settings, show title on all platforms unless it is explicitly hidden via hideTitle.
// For top level settings (not selected), show title on mobile + small screen web only.
const isWebSmallScreen = media.sm && isInterface
const shouldShowTitle = SelectedSetting ? !SelectedSetting.hideTitle : isMobileApp || isWebSmallScreen
// Hide close button on desktop web and extension unless there is custom button text
const shouldShowCloseButton = isMobileApp || isWebSmallScreen || Boolean(SelectedSetting?.renderCloseButtonText)
return ( return (
<Flex gap="$spacing16" px={isWeb ? '$spacing4' : '$spacing24'} py={isWeb ? '$spacing4' : '$spacing12'} width="100%"> <Flex gap="$spacing16" px={isWeb ? '$spacing4' : '$spacing24'} py={isWeb ? '$spacing4' : '$spacing12'} width="100%">
{shouldShowTitle && ( {!SelectedSetting?.hideTitle && (
<Flex row justifyContent="space-between" pt={isWeb ? '$spacing8' : 0}> <Flex row justifyContent="space-between">
<TouchableArea onPress={(): void => setSelectedSetting(undefined)}> <TouchableArea onPress={(): void => setSelectedSetting(undefined)}>
<RotatableChevron <RotatableChevron
color={ color={
...@@ -85,103 +104,23 @@ const TransactionSettingsModalContent = ({ ...@@ -85,103 +104,23 @@ const TransactionSettingsModalContent = ({
</Flex> </Flex>
)} )}
{screen} {screen}
{shouldShowCloseButton && ( <Flex centered row>
<Flex centered row pb={isWebSmallScreen ? '$spacing24' : '$spacing8'}> <DeprecatedButton fill testID="swap-settings-close" theme="secondary" onPress={onClose}>
<DeprecatedButton fill testID="swap-settings-close" theme="secondary" onPress={onClose}> {SelectedSetting?.renderCloseButtonText ? SelectedSetting.renderCloseButtonText(t) : t('common.button.close')}
{SelectedSetting?.renderCloseButtonText </DeprecatedButton>
? SelectedSetting.renderCloseButtonText(t) </Flex>
: t('common.button.save')}
</DeprecatedButton>
</Flex>
)}
</Flex> </Flex>
) )
} }
const TopLevelSettings = ({
settings,
setSelectedSetting,
}: {
settings: SwapSettingConfig[]
setSelectedSetting: React.Dispatch<React.SetStateAction<SwapSettingConfig | undefined>>
}): JSX.Element => {
const { t } = useTranslation()
const { customSlippageTolerance, customDeadline } = useTransactionSettingsContext()
const { autoSlippageTolerance } = useSlippageSettings()
const rowWarningContent = useMemo(
() =>
createRowWarningContent({
t,
customSlippageTolerance,
autoSlippageTolerance,
customDeadline,
}),
[t, customSlippageTolerance, autoSlippageTolerance, customDeadline],
)
const getSettingsRowWarning = (settingId: SwapSettingId): JSX.Element | undefined => {
const warning = rowWarningContent[settingId]
return warning?.condition ? warning.render() : undefined
}
return (
<Flex gap={isWeb ? '$spacing4' : '$spacing8'} py={isWeb ? '$spacing8' : '$spacing12'}>
{settings.map((setting, index) => {
const warning = setting.settingId ? getSettingsRowWarning(setting.settingId) : undefined
return (
<SwapSettingRow
key={`swap-setting-${index}`}
setSelectedSetting={setSelectedSetting}
setting={setting}
warning={warning}
/>
)
})}
</Flex>
)
}
function createRowWarningContent({
t,
autoSlippageTolerance,
customSlippageTolerance,
customDeadline,
}: {
t: TFunction
autoSlippageTolerance: number
customSlippageTolerance?: number
customDeadline?: number
}): Record<SwapSettingId, { condition: boolean; render: () => JSX.Element | undefined }> {
const isCriticalSlippage = Boolean(customSlippageTolerance && customSlippageTolerance >= SLIPPAGE_CRITICAL_TOLERANCE)
return {
[SwapSettingId.SLIPPAGE]: {
condition: !!customSlippageTolerance && customSlippageTolerance > autoSlippageTolerance,
render: () => (
<WarningMessage
warningMessage={isCriticalSlippage ? t('swap.settings.slippage.warning') : t('swap.settings.slippage.alert')}
tooltipText={isWeb && !isMobileWeb ? t('swap.settings.slippage.warning.hover') : undefined}
color={isCriticalSlippage ? '$statusCritical' : '$statusWarning'}
/>
),
},
[SwapSettingId.DEADLINE]: {
condition: !!customDeadline && customDeadline >= WARNING_DEADLINE_TOLERANCE,
render: () => <WarningMessage warningMessage={t('swap.settings.deadline.warning')} color="$statusWarning" />,
},
}
}
function TransactionSettingsModalInterface({ function TransactionSettingsModalInterface({
settings, settings,
defaultTitle, defaultTitle,
initialSelectedSetting, initialSelectedSetting,
onClose, onClose,
isOpen,
}: TransactionSettingsModalProps): JSX.Element { }: TransactionSettingsModalProps): JSX.Element {
return ( return (
<AdaptiveWebPopoverContent <Popover.Content
animation={[ animation={[
'quick', 'quick',
{ {
...@@ -195,14 +134,11 @@ function TransactionSettingsModalInterface({ ...@@ -195,14 +134,11 @@ function TransactionSettingsModalInterface({
borderWidth="$spacing1" borderWidth="$spacing1"
enterStyle={{ y: -10, opacity: 0 }} enterStyle={{ y: -10, opacity: 0 }}
exitStyle={{ y: -10, opacity: 0 }} exitStyle={{ y: -10, opacity: 0 }}
px="$spacing12" p="$spacing12"
py="$spacing4"
shadowColor="$shadowColor" shadowColor="$shadowColor"
shadowOpacity={0.06} shadowOpacity={0.06}
shadowRadius={6} shadowRadius={6}
width={POPOVER_WIDTH} width={POPOVER_WIDTH}
isOpen={isOpen}
webBottomSheetProps={{ px: '$padding16' }}
> >
<TransactionSettingsModalContent <TransactionSettingsModalContent
defaultTitle={defaultTitle} defaultTitle={defaultTitle}
...@@ -210,7 +146,7 @@ function TransactionSettingsModalInterface({ ...@@ -210,7 +146,7 @@ function TransactionSettingsModalInterface({
settings={settings} settings={settings}
onClose={onClose} onClose={onClose}
/> />
</AdaptiveWebPopoverContent> </Popover.Content>
) )
} }
......
...@@ -27,7 +27,6 @@ function isDefaultOptions(selectedProtocols: FrontendSupportedProtocol[]): boole ...@@ -27,7 +27,6 @@ function isDefaultOptions(selectedProtocols: FrontendSupportedProtocol[]): boole
export const ProtocolPreference: SwapSettingConfig = { export const ProtocolPreference: SwapSettingConfig = {
renderTitle: (t) => t('swap.settings.routingPreference.title'), renderTitle: (t) => t('swap.settings.routingPreference.title'),
renderCloseButtonText: (t) => t('common.button.save'),
Control() { Control() {
const { t } = useTranslation() const { t } = useTranslation()
const { selectedProtocols } = useTransactionSettingsContext() const { selectedProtocols } = useTransactionSettingsContext()
......
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { import {
selectHasSeenUnichainPromotionBridgingAnimation,
selectHasSeenUnichainPromotionBridgingTooltip, selectHasSeenUnichainPromotionBridgingTooltip,
selectHasSeenUnichainPromotionNetworkSelectorTooltip, selectHasSeenUnichainPromotionNetworkSelectorTooltip,
} from 'uniswap/src/features/behaviorHistory/selectors' } from 'uniswap/src/features/behaviorHistory/selectors'
...@@ -11,7 +10,6 @@ import { selectIsTestnetModeEnabled } from 'uniswap/src/features/settings/select ...@@ -11,7 +10,6 @@ import { selectIsTestnetModeEnabled } from 'uniswap/src/features/settings/select
export function useUnichainTooltipVisibility(): { export function useUnichainTooltipVisibility(): {
shouldShowUnichainNetworkSelectorTooltip: boolean shouldShowUnichainNetworkSelectorTooltip: boolean
shouldShowUnichainBridgingTooltip: boolean shouldShowUnichainBridgingTooltip: boolean
shouldShowUnichainBridgingAnimation: boolean
} { } {
const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain) const unichainEnabled = useFeatureFlag(FeatureFlags.Unichain)
const unichainPromoEnabled = useFeatureFlag(FeatureFlags.UnichainPromo) const unichainPromoEnabled = useFeatureFlag(FeatureFlags.UnichainPromo)
...@@ -19,7 +17,6 @@ export function useUnichainTooltipVisibility(): { ...@@ -19,7 +17,6 @@ export function useUnichainTooltipVisibility(): {
selectHasSeenUnichainPromotionNetworkSelectorTooltip, selectHasSeenUnichainPromotionNetworkSelectorTooltip,
) )
const hasSeenUnichainPromotionBridgingTooltip = useSelector(selectHasSeenUnichainPromotionBridgingTooltip) const hasSeenUnichainPromotionBridgingTooltip = useSelector(selectHasSeenUnichainPromotionBridgingTooltip)
const hasSeenUnichainPromotionBridgingAnimation = useSelector(selectHasSeenUnichainPromotionBridgingAnimation)
const isTestnetModeEnabled = useSelector(selectIsTestnetModeEnabled) const isTestnetModeEnabled = useSelector(selectIsTestnetModeEnabled)
// Don't show promotion if: // Don't show promotion if:
...@@ -30,13 +27,11 @@ export function useUnichainTooltipVisibility(): { ...@@ -30,13 +27,11 @@ export function useUnichainTooltipVisibility(): {
return { return {
shouldShowUnichainNetworkSelectorTooltip: false, shouldShowUnichainNetworkSelectorTooltip: false,
shouldShowUnichainBridgingTooltip: false, shouldShowUnichainBridgingTooltip: false,
shouldShowUnichainBridgingAnimation: false,
} }
} }
return { return {
shouldShowUnichainNetworkSelectorTooltip: !hasSeenUnichainPromotionNetworkSelectorTooltip, shouldShowUnichainNetworkSelectorTooltip: !hasSeenUnichainPromotionNetworkSelectorTooltip,
shouldShowUnichainBridgingTooltip: !hasSeenUnichainPromotionBridgingTooltip, shouldShowUnichainBridgingTooltip: !hasSeenUnichainPromotionBridgingTooltip,
shouldShowUnichainBridgingAnimation: !hasSeenUnichainPromotionBridgingAnimation,
} }
} }
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