ci(release): publish latest release

parent fbe0233c
* @uniswap/web-admins
IPFS hash of the deployment:
- CIDv0: `Qmb16dcqcbFFjG7vkpfKEeZp4fNHBcbzXwuPLW92MQNRAx`
- CIDv1: `bafybeif4ewp6t3m3h2yfzmgwe6o3lcccxeec5asf5xkezonc6vwcpxw5de`
We are back with some new new updates! Here’s the latest:
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
UniswapX is live: We’ve integrated the UniswapX protocol, which aggregates liquidity across onchain and offchain sources for better quotes.
You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.
Easy import to Uniswap Extension: Onboard onto our new Chrome extension wallet easily by scanning a QR code with your Uniswap Mobile App.
IPFS gateways:
- https://bafybeif4ewp6t3m3h2yfzmgwe6o3lcccxeec5asf5xkezonc6vwcpxw5de.ipfs.dweb.link/
- https://bafybeif4ewp6t3m3h2yfzmgwe6o3lcccxeec5asf5xkezonc6vwcpxw5de.ipfs.cf-ipfs.com/
- [ipfs://Qmb16dcqcbFFjG7vkpfKEeZp4fNHBcbzXwuPLW92MQNRAx/](ipfs://Qmb16dcqcbFFjG7vkpfKEeZp4fNHBcbzXwuPLW92MQNRAx/)
### 5.40.3 (2024-07-25)
Transaction Details: Press anything on the Activity Screen and see more robust details about any of your transactions (swaps, sends, NFTs, etc).
Other changes:
- Onboarding improvements
- Various bug fixes and performance improvements
web/5.40.3
\ No newline at end of file
mobile/1.31.1
\ No newline at end of file
......@@ -131,17 +131,17 @@ android {
dev {
isDefault(true)
applicationIdSuffix ".dev"
versionName "1.31"
versionName "1.31.1"
dimension "variant"
}
beta {
applicationIdSuffix ".beta"
versionName "1.31"
versionName "1.31.1"
dimension "variant"
}
prod {
dimension "variant"
versionName "1.31"
versionName "1.31.1"
}
}
......
......@@ -2534,7 +2534,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2580,7 +2580,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets;
......@@ -2626,7 +2626,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets;
......@@ -2672,7 +2672,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets;
......@@ -2714,7 +2714,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -2757,7 +2757,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension;
......@@ -2800,7 +2800,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension;
......@@ -2843,7 +2843,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension;
......@@ -2879,7 +2879,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -2917,7 +2917,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3103,7 +3103,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
......@@ -3147,7 +3147,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension;
......@@ -3251,7 +3251,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3322,7 +3322,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension;
......@@ -3426,7 +3426,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
......@@ -3497,7 +3497,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.31;
MARKETING_VERSION = 1.31.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension;
......
......@@ -66,6 +66,7 @@ import {
v63Schema,
v64Schema,
v65Schema,
v66Schema,
v6Schema,
v7Schema,
v8Schema,
......@@ -1407,4 +1408,11 @@ describe('Redux state migrations', () => {
const v66 = migrations[66]
testActivatePendingAccounts(v66, v65Schema)
})
it('migrates from v66 to v67', () => {
const v66Stub = { ...v66Schema }
const v67 = migrations[67](v66Stub)
expect(v67.behaviorHistory.extensionOnboardingState).toBe(ExtensionOnboardingState.Undefined)
})
})
......@@ -877,6 +877,18 @@ export const migrations = {
65: addRoutingFieldToTransactions,
66: activatePendingAccounts,
67: function resetOnboardingStateForGA(state: any) {
const newState = { ...state }
// Reset state so that everyone gets the new promo banner even if theyve dismissed the beta version.
newState.behaviorHistory = {
...state.behaviorHistory,
extensionOnboardingState: ExtensionOnboardingState.Undefined,
}
return newState
},
}
export const MOBILE_STATE_VERSION = 66
export const MOBILE_STATE_VERSION = 67
......@@ -505,6 +505,8 @@ export const v65Schema = { ...v64Schema }
export const v66Schema = { ...v65Schema }
export const v67Schema = { ...v66Schema }
// TODO: [MOB-201] use function with typed output when API reducers are removed from rootReducer
// export const getSchema = (): RootState => v0Schema
export const getSchema = (): typeof v65Schema => v65Schema
export const getSchema = (): typeof v67Schema => v67Schema
......@@ -102,7 +102,7 @@ export function WalletConnectRequestModalContent({
<Flex gap="$spacing8" mb="$spacing12" pt="$spacing20" px="$spacing4">
<NetworkFeeFooter chainId={chainId} gasFeeUSD={hasGasFee ? gasFeeUSD : '0'} showNetworkLogo={hasGasFee} />
<AddressFooter activeAccountAddress={request.account} />
<AddressFooter activeAccountAddress={request.account} px="$spacing8" />
</Flex>
{!hasSufficientFunds && (
......
......@@ -141,7 +141,7 @@ const SwitchAccountRow = ({ activeAddress, setModalState }: SwitchAccountProps):
return (
<TouchableArea disabled={!accountIsSwitchable} m="$none" testID={TestID.WCDappSwitchAccount} onPress={onPress}>
<Flex row justifyContent="space-between">
<AddressFooter activeAccountAddress={activeAddress} />
<AddressFooter activeAccountAddress={activeAddress} px="$spacing8" />
{accountIsSwitchable && <RotatableChevron color="$neutral2" direction="down" height={16} width={16} />}
</Flex>
</TouchableArea>
......
......@@ -48,14 +48,13 @@ export const ActivityTab = memo(
dispatch(openModal({ name: ModalName.WalletConnectScan, initialState: ScannerModalState.WalletQr }))
}
const { maybeLoaderComponent, maybeEmptyComponent, renderActivityItem, sectionData, keyExtractor } =
useActivityData({
owner,
authTrigger: requiresBiometrics ? biometricsTrigger : undefined,
isExternalProfile,
emptyComponentStyle: containerProps?.emptyComponentStyle,
onPressEmptyState: onPressReceive,
})
const { maybeEmptyComponent, renderActivityItem, sectionData, keyExtractor } = useActivityData({
owner,
authTrigger: requiresBiometrics ? biometricsTrigger : undefined,
isExternalProfile,
emptyComponentStyle: containerProps?.emptyComponentStyle,
onPressEmptyState: onPressReceive,
})
const refreshControl = useMemo(() => {
return (
......@@ -77,12 +76,8 @@ export const ActivityTab = memo(
ref={ref as ForwardedRef<Animated.FlatList<any>>}
ListEmptyComponent={maybeEmptyComponent}
// we add a footer to cover any possible space, so user can scroll the top menu all the way to the top
ListFooterComponent={
<>
{maybeLoaderComponent}
{isExternalProfile ? null : adaptiveFooter}
</>
}
ListFooterComponent={isExternalProfile ? null : adaptiveFooter}
// `sectionData` will be either an array of transactions or an array of loading skeletons
data={sectionData}
estimatedItemSize={ESTIMATED_ITEM_SIZE}
initialNumToRender={20}
......
......@@ -16,7 +16,7 @@ function key(item: FORServiceProvider): string {
}
const CEX_ICON_SIZE = iconSizes.icon36
const CEX_ICON_BORDER_RADIUS = 12
const CEX_ICON_BORDER_RADIUS = 8
function CEXItemWrapper({
serviceProvider,
......
......@@ -16,6 +16,7 @@ import { RecoveryWalletInfo, useOnDeviceRecoveryData } from 'src/screens/Import/
import { hideSplashScreen } from 'src/utils/splashScreen'
import { DynamicConfigs, OnDeviceRecoveryConfigKey } from 'uniswap/src/features/gating/configs'
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { MobileEventName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
......@@ -133,6 +134,7 @@ export function AppLoadingScreen({ navigation }: Props): JSX.Element | null {
20,
)
// Used to stop this running multiple times during navigation
const [finished, setFinished] = useState(false)
const [mnemonicIds, setMnemonicIds] = useState<string[]>()
......@@ -148,7 +150,12 @@ export function AppLoadingScreen({ navigation }: Props): JSX.Element | null {
useEffect(() => {
Keyring.getMnemonicIds()
.then((storedMnemonicIds) => setMnemonicIds(storedMnemonicIds))
.then((storedMnemonicIds) => {
setMnemonicIds(storedMnemonicIds)
sendAnalyticsEvent(MobileEventName.AutomatedOnDeviceRecoveryMnemonicsFound, {
mnemonicCount: storedMnemonicIds.length,
})
})
.catch(() => {
logger.error('Failed to load mnemonic ids', {
tags: { file: 'AppLoadingScreen', function: 'getMnemonicIds' },
......@@ -170,36 +177,41 @@ export function AppLoadingScreen({ navigation }: Props): JSX.Element | null {
// Logic to determine what screen to show on app load
useEffect(() => {
async function checkOnDeviceRecovery(): Promise<void> {
if (!mnemonicIds || loading || finished) {
if (!mnemonicIds || finished) {
return
}
const mnemonicIdsCount = mnemonicIds.length
const firstMnemonicId = mnemonicIds[0]
if (mnemonicIdsCount === 1 && firstMnemonicId) {
if (loading) {
return
}
const mnemonicIdsCount = mnemonicIds.length
const firstMnemonicId = mnemonicIds[0]
// Used to stop this running multiple times as the following logic is async
setFinished(true)
if (mnemonicIdsCount === 1 && firstMnemonicId) {
if (significantRecoveryWalletInfos.length) {
finishRecovery(firstMnemonicId, significantRecoveryWalletInfos)
} else {
navigateToLanding()
}
} else if (mnemonicIdsCount > 1) {
navigation.replace(OnboardingScreens.OnDeviceRecovery, {
importType: ImportType.OnDeviceRecovery,
entryPoint: OnboardingEntryPoint.FreshInstallOrReplace,
mnemonicIds: mnemonicIds.slice(0, maxMnemonicsToLoad),
})
sendAnalyticsEvent(MobileEventName.AutomatedOnDeviceRecoverySingleMnemonicFetched, {
balance: significantRecoveryWalletInfos[0]?.balance ?? 0,
hasUnitag: Boolean(significantRecoveryWalletInfos[0]?.unitag),
hasENS: Boolean(significantRecoveryWalletInfos[0]?.ensName),
})
if (significantRecoveryWalletInfos.length) {
finishRecovery(firstMnemonicId, significantRecoveryWalletInfos)
} else {
navigateToLanding()
}
} else if (mnemonicIdsCount > 1) {
setFinished(true)
navigation.replace(OnboardingScreens.OnDeviceRecovery, {
importType: ImportType.OnDeviceRecovery,
entryPoint: OnboardingEntryPoint.FreshInstallOrReplace,
mnemonicIds: mnemonicIds.slice(0, maxMnemonicsToLoad),
})
} else {
setFinished(true)
navigateToLanding()
}
checkOnDeviceRecovery().catch(() => {
logger.warn('AppLoadingScreen', 'checkOnDeviceRecovery', 'Failed to check on device recovery')
})
}, [
dispatch,
finishRecovery,
......@@ -212,5 +224,9 @@ export function AppLoadingScreen({ navigation }: Props): JSX.Element | null {
significantRecoveryWalletInfos,
])
return <SplashScreen />
return (
<Trace logImpression screen={OnboardingScreens.AppLoading}>
<SplashScreen />
</Trace>
)
}
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { SharedEventName } from '@uniswap/analytics-events'
import dayjs from 'dayjs'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
......@@ -18,7 +19,8 @@ import { iconSizes } from 'ui/src/theme'
import { DynamicConfigs, OnDeviceRecoveryConfigKey } from 'uniswap/src/features/gating/configs'
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { TestID } from 'uniswap/src/test/fixtures/testIDs'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { OnboardingScreens } from 'uniswap/src/types/screens/mobile'
......@@ -105,6 +107,10 @@ export function OnDeviceRecoveryScreen({
setSelectedMnemonicId(undefined)
setSelectedRecoveryWalletInfos([])
setShowConfirmationModal(false)
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
element: ElementName.OnDeviceRecoveryModalCancel,
})
}
const onPressConfirm = async (): Promise<void> => {
......@@ -112,6 +118,10 @@ export function OnDeviceRecoveryScreen({
await clearNonSelectedStoredAddresses()
setShowConfirmationModal(false)
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
element: ElementName.OnDeviceRecoveryModalConfirm,
})
if (selectedMnemonicId && selectedRecoveryWalletInfos.length) {
setRecoveredImportedAccounts(
selectedRecoveryWalletInfos.map((walletInfo, index): SignerMnemonicAccount => {
......@@ -187,6 +197,10 @@ export function OnDeviceRecoveryScreen({
setSelectedMnemonicId(mnemonicId)
setSelectedRecoveryWalletInfos(recoveryAddressesInfos)
setShowConfirmationModal(true)
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
element: ElementName.OnDeviceRecoveryWallet,
})
}}
onPressViewRecoveryPhrase={() => {
navigation.navigate(OnboardingScreens.OnDeviceRecoveryViewSeedPhrase, {
......@@ -214,11 +228,13 @@ export function OnDeviceRecoveryScreen({
<Text color="$neutral3" variant="body3" onPress={onPressOtherWallet}>
{t('onboarding.import.onDeviceRecovery.other_options.label')}
</Text>
<TouchableArea alignItems="center" hitSlop={16} mb="$spacing12" testID={TestID.WatchWallet}>
<Text color="$accent1" variant="buttonLabel3" onPress={onPressOtherWallet}>
{t('onboarding.import.onDeviceRecovery.other_options')}
</Text>
</TouchableArea>
<Trace logPress element={ElementName.OnDeviceRecoveryImportOther}>
<TouchableArea alignItems="center" hitSlop={16} mb="$spacing12" testID={TestID.WatchWallet}>
<Text color="$accent1" variant="buttonLabel3" onPress={onPressOtherWallet}>
{t('onboarding.import.onDeviceRecovery.other_options')}
</Text>
</TouchableArea>
</Trace>
</Flex>
</Flex>
</Flex>
......
......@@ -31,21 +31,21 @@ type Props = NativeStackScreenProps<OnboardingStackParamList, OnboardingScreens.
const LIVE_CHECK_DELAY = 1000
const validateForm = ({
isAddress,
validAddress,
name,
walletExists,
loading,
isSmartContractAddress,
isValidSmartContract,
}: {
isAddress: string | null
validAddress: string | null
name: string | null
walletExists: boolean
loading: boolean
isSmartContractAddress: boolean
isValidSmartContract: boolean
}): boolean => {
return (!!isAddress || !!name) && !walletExists && !loading && (!isSmartContractAddress || isValidSmartContract)
return (!!validAddress || !!name) && !walletExists && !loading && (!isSmartContractAddress || isValidSmartContract)
}
const getErrorText = ({
......@@ -85,28 +85,28 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX
const normalizedValue = normalizeTextInput(value ?? '')
const hasSuffixIncluded = normalizedValue.includes('.')
const { address: resolvedAddress, name } = useENS(UniverseChainId.Mainnet, normalizedValue, !hasSuffixIncluded)
const isAddress = getValidAddress(normalizedValue, true, false)
const validAddress = getValidAddress(normalizedValue, true, false)
const { isSmartContractAddress, loading } = useIsSmartContractAddress(
(isAddress || resolvedAddress) ?? undefined,
(validAddress || resolvedAddress) ?? undefined,
UniverseChainId.Mainnet,
)
// Allow smart contracts with non-null balances
const { data: balancesById } = usePortfolioBalances({
address: isSmartContractAddress ? (isAddress || resolvedAddress) ?? undefined : undefined,
address: isSmartContractAddress ? (validAddress || resolvedAddress) ?? undefined : undefined,
fetchPolicy: 'cache-and-network',
})
const isValidSmartContract = isSmartContractAddress && !!balancesById
const onCompleteOnboarding = useCompleteOnboardingCallback(params)
const walletExists = Object.keys(initialAccounts).some(
const walletExists = Object.keys(initialAccounts.current).some(
(accountAddress) =>
areAddressesEqual(accountAddress, resolvedAddress) || areAddressesEqual(accountAddress, normalizedValue),
areAddressesEqual(accountAddress, resolvedAddress) || areAddressesEqual(accountAddress, validAddress),
)
// Form validation.
const isValid = validateForm({
isAddress,
validAddress,
name,
walletExists,
loading,
......@@ -159,7 +159,7 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX
blurOnSubmit
errorMessage={errorText}
inputAlignment="flex-start"
inputSuffix={isAddress || hasSuffixIncluded ? undefined : '.eth'}
inputSuffix={validAddress || hasSuffixIncluded ? undefined : '.eth'}
liveCheck={showLiveCheck}
placeholderLabel={t('account.wallet.watch.placeholder')}
shouldUseMinHeight={false}
......@@ -185,7 +185,7 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX
</Text>
</Flex>
</Flex>
<Button disabled={!isValid} testID={TestID.Next} onPress={onSubmit}>
<Button disabled={!isValid} mt="$spacing24" testID={TestID.Next} onPress={onSubmit}>
{t('common.button.continue')}
</Button>
</SafeKeyboardOnboardingScreen>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -20,7 +20,7 @@ export function AddressDisplay({ address, enableCopyAddress }: { address: Addres
const uniswapUsername = unitag?.username
const AddressDisplay = (
<Flex row gap="2px" alignItems="center">
<Flex row gap="2px">
<IdentifierText>{uniswapUsername ?? ENSName ?? shortenAddress(address)}</IdentifierText>
{uniswapUsername && <Unitag size={18} />}
</Flex>
......
......@@ -2,7 +2,6 @@ import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer/
import { useOpenLimitOrders, usePendingActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks'
import { useFilterPossiblyMaliciousPositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools'
import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks'
import { Pool } from 'components/Icons/Pool'
import { ExtensionRequestMethods, useUniswapExtensionConnector } from 'components/WalletModal/useOrderedConnections'
import { t } from 'i18n'
......@@ -37,13 +36,12 @@ const DeepLinkButton = ({ Icon, Label, onPress }: { Icon: JSX.Element; Label: st
py="$spacing12"
fontSize="$large"
onPress={onPress}
hoverStyle={{ opacity: 0.9 }}
>
{Icon}
<Text display="flex" flex={1} variant="buttonLabel3">
{Label}
</Text>
<RotatableChevron width={iconSizes.icon20} height={iconSizes.icon20} color="$neutral3" direction="right" />
<RotatableChevron width={iconSizes.icon20} height={iconSizes.icon20} color="$neutral1" direction="right" />
</Button>
)
}
......@@ -51,7 +49,6 @@ const DeepLinkButton = ({ Icon, Label, onPress }: { Icon: JSX.Element; Label: st
export function ExtensionDeeplinks({ account }: { account: string }) {
const theme = useTheme()
const uniswapExtensionConnector = useUniswapExtensionConnector()
const accountDrawer = useAccountDrawer()
const setMenu = useUpdateAtom(miniPortfolioMenuStateAtom)
const { openLimitOrders } = useOpenLimitOrders(account)
......@@ -77,7 +74,6 @@ export function ExtensionDeeplinks({ account }: { account: string }) {
Label={t('extension.open')}
onPress={() => {
uniswapExtensionConnector.extensionRequest(ExtensionRequestMethods.OPEN_SIDEBAR, 'Tokens')
accountDrawer.close()
}}
/>
<DeepLinkButton
......@@ -90,7 +86,6 @@ export function ExtensionDeeplinks({ account }: { account: string }) {
Label={t('common.activity')}
onPress={() => {
uniswapExtensionConnector.extensionRequest(ExtensionRequestMethods.OPEN_SIDEBAR, 'Activity')
accountDrawer.close()
setActivityUnread(false)
}}
/>
......
......@@ -135,7 +135,7 @@ export function Pending({
if (swapResult && swapResult.type === TradeFillType.Classic) {
txHash = swapResult.response.hash
} else if (uniswapXOrder && uniswapXOrder.status === UniswapXOrderStatus.FILLED) {
txHash = uniswapXOrder.txHash
txHash = uniswapXOrder.orderHash
} else {
return
}
......
import { ComponentProps } from 'react'
export function BraveBrowserLogo(props: ComponentProps<'svg'>) {
return (
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M57.622 15.321L59.122 11.645C59.122 11.645 57.213 9.602 54.895 7.287C52.578 4.972 47.67 6.334 47.67 6.334L42.081 0H22.45L16.861 6.334C16.861 6.334 11.953 4.972 9.636 7.287C7.318 9.602 5.41 11.645 5.41 11.645L6.91 15.321L5 20.768C5 20.768 10.614 42.004 11.272 44.598C12.567 49.704 13.453 51.678 17.134 54.266C20.814 56.853 27.494 61.346 28.584 62.028C29.675 62.708 31.039 63.868 32.266 63.868C33.493 63.868 34.856 62.708 35.946 62.028C37.037 61.347 43.716 56.853 47.398 54.266C51.078 51.679 51.965 49.704 53.26 44.598C53.917 42.004 59.53 20.768 59.53 20.768L57.622 15.321Z"
fill="url(#paint0_linear_11_3292)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M39.218 11.508C40.036 11.508 46.103 10.351 46.103 10.351C46.103 10.351 53.292 19.031 53.292 20.887C53.292 22.421 52.673 23.021 51.945 23.729C51.793 23.877 51.635 24.029 51.478 24.197L46.088 29.914C46.0301 29.9748 45.9715 30.0348 45.912 30.094C45.374 30.634 44.582 31.43 45.14 32.752L45.255 33.021C45.868 34.453 46.625 36.221 45.662 38.011C44.637 39.917 42.882 41.189 41.757 40.978C40.633 40.768 37.991 39.389 37.02 38.76C36.049 38.13 32.97 35.594 32.97 34.623C32.97 33.814 35.184 32.468 36.26 31.813C36.474 31.683 36.643 31.581 36.74 31.515C36.851 31.44 37.037 31.325 37.266 31.183C38.247 30.573 40.02 29.473 40.065 28.986C40.12 28.384 40.099 28.208 39.307 26.722C39.139 26.406 38.942 26.068 38.739 25.718C37.985 24.423 37.141 22.973 37.329 21.934C37.539 20.761 39.379 20.089 40.937 19.519C41.131 19.449 41.322 19.379 41.504 19.31L43.127 18.701C44.683 18.119 46.411 17.472 46.697 17.341C47.091 17.16 46.989 16.986 45.794 16.873C45.6006 16.854 45.4072 16.834 45.214 16.813C43.734 16.656 41.005 16.367 39.679 16.736C39.418 16.809 39.126 16.888 38.819 16.971C37.329 17.374 35.502 17.868 35.326 18.153C35.296 18.203 35.266 18.246 35.237 18.286C35.069 18.524 34.96 18.68 35.146 19.692C35.201 19.994 35.315 20.587 35.456 21.321C35.866 23.469 36.509 26.819 36.59 27.571C36.601 27.677 36.614 27.778 36.626 27.876C36.729 28.716 36.797 29.275 35.821 29.498L35.566 29.556C34.464 29.808 32.849 30.179 32.266 30.179C31.682 30.179 30.066 29.809 28.964 29.556L28.71 29.498C27.734 29.275 27.803 28.716 27.906 27.876C27.918 27.778 27.93 27.676 27.941 27.571C28.022 26.818 28.666 23.459 29.078 21.312C29.218 20.582 29.331 19.992 29.386 19.692C29.571 18.68 29.462 18.524 29.294 18.286C29.263 18.2423 29.233 18.198 29.204 18.153C29.03 17.868 27.204 17.374 25.713 16.971C25.406 16.888 25.113 16.809 24.853 16.736C23.526 16.366 20.798 16.656 19.318 16.813C19.092 16.837 18.896 16.858 18.738 16.873C17.542 16.986 17.441 17.16 17.835 17.341C18.12 17.472 19.848 18.119 21.403 18.701C22 18.924 22.573 19.138 23.027 19.31C23.21 19.379 23.4 19.448 23.595 19.52C25.153 20.09 26.993 20.761 27.203 21.934C27.39 22.973 26.546 24.423 25.793 25.718C25.589 26.068 25.393 26.406 25.224 26.722C24.433 28.208 24.412 28.384 24.467 28.986C24.511 29.474 26.283 30.573 27.265 31.183C27.494 31.325 27.68 31.44 27.791 31.515C27.889 31.581 28.057 31.683 28.271 31.813C29.347 32.467 31.561 33.813 31.561 34.623C31.561 35.593 28.483 38.13 27.511 38.76C26.541 39.39 23.899 40.768 22.774 40.978C21.65 41.188 19.894 39.917 18.87 38.012C17.907 36.221 18.663 34.453 19.276 33.022L19.391 32.752C19.95 31.43 19.158 30.634 18.619 30.094C18.5599 30.0348 18.5015 29.9748 18.444 29.914L13.054 24.197C12.896 24.03 12.738 23.877 12.586 23.729C11.858 23.022 11.24 22.421 11.24 20.887C11.24 19.032 18.429 10.351 18.429 10.351C18.429 10.351 24.495 11.508 25.313 11.508C25.966 11.508 27.226 11.075 28.54 10.623C28.873 10.509 29.209 10.393 29.54 10.283C31.175 9.73801 32.266 9.73401 32.266 9.73401C32.266 9.73401 33.356 9.73801 34.992 10.283C35.322 10.393 35.659 10.509 35.992 10.623C37.305 11.075 38.566 11.508 39.218 11.508ZM38.177 42.214C39.459 42.874 40.369 43.342 40.713 43.557C41.158 43.835 40.887 44.36 40.481 44.647C40.076 44.932 34.628 49.146 34.1 49.612L33.885 49.803C33.376 50.262 32.726 50.847 32.265 50.847C31.805 50.847 31.155 50.261 30.645 49.803L30.432 49.612C29.902 49.146 24.455 44.932 24.05 44.646C23.645 44.36 23.373 43.836 23.818 43.556C24.162 43.342 25.073 42.873 26.357 42.212L27.577 41.583C29.497 40.591 31.892 39.746 32.266 39.746C32.639 39.746 35.033 40.59 36.955 41.583C37.391 41.809 37.8 42.021 38.177 42.214Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M47.67 6.334L42.081 0H22.45L16.861 6.334C16.861 6.334 11.953 4.972 9.63605 7.287C9.63605 7.287 16.18 6.697 18.429 10.351C18.429 10.351 24.495 11.508 25.313 11.508C26.131 11.508 27.903 10.828 29.539 10.283C31.175 9.738 32.266 9.734 32.266 9.734C32.266 9.734 33.356 9.738 34.992 10.283C36.628 10.828 38.4 11.508 39.218 11.508C40.036 11.508 46.103 10.351 46.103 10.351C48.352 6.697 54.895 7.287 54.895 7.287C52.578 4.972 47.67 6.334 47.67 6.334Z"
fill="url(#paint1_linear_11_3292)"
/>
<defs>
<linearGradient
id="paint0_linear_11_3292"
x1="5.001"
y1="64.319"
x2="59.53"
y2="64.319"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FF5500" />
<stop offset="0.41" stopColor="#FF5500" />
<stop offset="0.582" stopColor="#FF2000" />
<stop offset="1" stopColor="#FF2000" />
</linearGradient>
<linearGradient
id="paint1_linear_11_3292"
x1="10.608"
y1="11.466"
x2="54.895"
y2="11.466"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FF452A" />
<stop offset="1" stopColor="#FF2000" />
</linearGradient>
</defs>
</svg>
)
}
import { ColumnCenter } from 'components/Column'
import { MobileAppLogo } from 'components/Icons/MobileAppLogo'
import { PropsWithChildren } from 'react'
import { ThemedText } from 'theme/components'
import { Image } from 'ui/src'
import { UNISWAP_LOGO } from 'ui/src/assets'
import { iconSizes } from 'ui/src/theme'
export function ModalContent({ title, subtext, children }: PropsWithChildren<{ title: string; subtext: string }>) {
return (
<ColumnCenter gap="xl">
<ColumnCenter gap="md">
<Image height={iconSizes.icon64} source={UNISWAP_LOGO} width={iconSizes.icon64} />
<MobileAppLogo width={64} height={64} />
<ColumnCenter gap="sm">
<ThemedText.H1Medium textAlign="center">{title}</ThemedText.H1Medium>
<ThemedText.BodySecondary textAlign="center" maxWidth="400px">
......
......@@ -3,6 +3,7 @@ import ExtensionIllustration from 'assets/images/extensionIllustration.png'
import WalletIllustration from 'assets/images/walletIllustration.png'
import Column from 'components/Column'
import { AppleLogo } from 'components/Icons/AppleLogo'
import { BraveBrowserLogo } from 'components/Icons/BraveBrowserLogo'
import { GoogleChromeLogo } from 'components/Icons/GoogleChromeLogo'
import { GooglePlayStoreLogo } from 'components/Icons/GooglePlayStoreLogo'
import { WiggleIcon } from 'components/NavBar/DownloadApp/GetTheAppButton'
......@@ -64,7 +65,7 @@ export function GetStarted({ toAppDownload }: { toAppDownload: () => void }) {
<IllustrationContainer>
<Illustration src={WalletIllustration} alt="Wallet example page" />
</IllustrationContainer>
<CardInfo title={t('common.uniswapMobile')} details={t('common.iOSAndroid')}>
<CardInfo title={t('common.mobileWallet')} details={t('common.iOSAndroid')}>
<Row gap="8px" width="auto">
<WiggleIcon>
<AppleLogo fill={theme.neutral1} />
......@@ -82,6 +83,9 @@ export function GetStarted({ toAppDownload }: { toAppDownload: () => void }) {
</IllustrationContainer>
<CardInfo title={t('common.chromeExtension')} details={t('common.googleChrome')}>
<Row gap="8px" width="auto">
<WiggleIcon>
<BraveBrowserLogo width="16px" height="16px" />
</WiggleIcon>
<WiggleIcon>
<GoogleChromeLogo width="16px" height="16px" />
</WiggleIcon>
......
......@@ -33,6 +33,7 @@ const Nav = styled.nav`
justify-content: center;
`
const NavContents = styled.div`
max-width: ${({ theme }) => `${theme.breakpoint.xxxl}px`};
width: 100%;
display: flex;
flex-direction: row;
......
......@@ -119,7 +119,7 @@ export default function SettingsTab({
compact?: boolean
hideRoutingSettings?: boolean
}) {
const showDeadlineSettings = !isL2ChainId(chainId)
const showDeadlineSettings = isL2ChainId(chainId)
const toggleButtonNode = useRef<HTMLDivElement | null>(null)
const menuNode = useRef<HTMLDivElement | null>(null)
const isOpen = useModalIsOpen(ApplicationModal.SETTINGS)
......
......@@ -6,7 +6,6 @@ import Modal from 'components/Modal'
import Row from 'components/Row'
import { AppIcon } from 'components/WalletModal/UniswapWalletOptions'
import { useIsMobile } from 'hooks/screenSize'
import useParsedQueryString from 'hooks/useParsedQueryString'
import { Trans } from 'i18n'
import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
......@@ -26,9 +25,6 @@ const ModalWrapper = styled.div`
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
flex-direction: column;
}
* {
outline: none;
}
`
const PromoImage = styled.img`
......@@ -39,7 +35,7 @@ const PromoImage = styled.img`
background: url('/images/extension_promo/announcement_modal_desktop.png');
background-repeat: no-repeat;
background-size: cover;
flex: 1;
flex-shrink: 0;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
background: url('/images/extension_promo/announcement_modal_mobile.png');
......@@ -47,7 +43,6 @@ const PromoImage = styled.img`
background-position: 50%;
height: 392px;
width: 100%;
flex: unset;
}
`
......@@ -62,7 +57,6 @@ const TextWrapper = styled(Column)`
padding: 20px 24px;
gap: 16px;
height: 100%;
flex: 1;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
gap: 12px;
......@@ -101,7 +95,6 @@ const showExtensionLaunchAtom = atomWithStorage('showUniswapExtensionLaunchAtom'
export function ExtensionLaunchModal() {
const [showExtensionLaunch, setShowExtensionLaunch] = useAtom(showExtensionLaunchAtom)
const isOnLandingPage = useParsedQueryString().intro === 'true'
const isMobile = useIsMobile()
return (
......@@ -109,8 +102,7 @@ export function ExtensionLaunchModal() {
<Modal
maxWidth={isMobile ? undefined : 520}
height={isMobile ? 564 : 320}
isOpen={showExtensionLaunch && !isOnLandingPage}
hideBorder
isOpen={showExtensionLaunch}
onDismiss={() => setShowExtensionLaunch(false)}
>
<ModalWrapper>
......
......@@ -54,10 +54,10 @@ export const DownloadWalletOption = () => {
<AppIcon src={UNIWALLET_ICON} alt="uniswap-app-icon" />
<Row gap="xs">
<Column>
<Text variant="buttonLabel3" color="$white" whiteSpace="nowrap">
<Text variant="buttonLabel3" color="$black" whiteSpace="nowrap">
<Trans i18nKey="common.getUniswapWallet" />
</Text>
<Text variant="body4" color="$white" whiteSpace="nowrap">
<Text variant="body4" color="$black" whiteSpace="nowrap">
<Trans i18nKey="common.availableOnIOSAndroidChrome" />
</Text>
</Column>
......
......@@ -10,7 +10,7 @@ const StyledLink = styled(ExternalLink)`
export default function PrivacyPolicyNotice() {
return (
<ThemedText.BodySmall color="neutral2">
<Trans i18nKey="wallet.connectingAgreement" />{' '}
<Trans i18nKey="wallet.connectingAgreement" />
<StyledLink href="https://uniswap.org/terms-of-service/">
<Trans i18nKey="common.termsOfService" />{' '}
</StyledLink>
......
......@@ -10,7 +10,7 @@ import styled from 'lib/styled-components'
import { BREAKPOINTS } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
import { Text } from 'ui/src'
import { ScanQr } from 'ui/src/components/icons'
import { Mobile, QrCode } from 'ui/src/components/icons'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
......@@ -79,17 +79,20 @@ export function UniswapWalletOptions() {
<DownloadWalletOption />
) : null}
<OptionContainer gap="md" onClick={() => connect({ connector: uniswapWalletConnectConnector })}>
<ScanQr size="$icon.40" minWidth={40} color="$accent1" backgroundColor="$accent2" borderRadius={8} p={7} />
<Mobile size="$icon.40" minWidth={40} color="$accent1" backgroundColor="$accent2" borderRadius={8} p={7} />
<Row gap="xs">
<Column>
<Text variant="buttonLabel3" color="$neutral1" whiteSpace="nowrap">
<Trans i18nKey="common.uniswapMobile" />
<Trans i18nKey="common.mobileWallet" />
</Text>
<Text variant="body4" color="$neutral2" whiteSpace="nowrap">
<Trans i18nKey="wallet.scanToConnect" />
</Text>
</Column>
</Row>
<TagContainer>
<QrCode size={20} color="$neutral2" />
</TagContainer>
</OptionContainer>
</Column>
</Column>
......
......@@ -75,7 +75,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
const showUniswapWalletOptions = useUniswapWalletOptions()
const connectors = useOrderedConnections(showUniswapWalletOptions)
const isUniExtensionAvailable = useIsUniExtensionAvailable()
const [showOtherWallets, toggleShowOtherWallets] = useReducer((s) => !s, true)
const [showOtherWallets, toggleShowOtherWallets] = useReducer((s) => !s, false)
return (
<Wrapper data-testid="wallet-modal" isUniExtensionAvailable={isUniExtensionAvailable}>
......
......@@ -288,6 +288,7 @@
"common.mint.failed": "Mint failed",
"common.minted": "Minted",
"common.minting": "Minting",
"common.mobileWallet": "Mobile Wallet",
"common.more": "More",
"common.navigationButton": "Navigation button",
"common.needHelp": "Need help?",
......@@ -485,7 +486,6 @@
"common.unavailable": "Unavailable",
"common.uniGovernance": "UNI Governance",
"common.uniInterface": "Uniswap Interface",
"common.uniswapMobile": "Uniswap Mobile",
"common.uniswapProtocol": "Uniswap Protocol",
"common.uniswapTVL": "Uniswap TVL",
"common.uniswapWallet": "Uniswap wallet",
......@@ -583,7 +583,7 @@
"explore.unableToDisplayHistorical": "Unable to display historical volume data for the current chain.",
"explore.unableToDisplayHistoricalTVL": "Unable to display historical TVL data for the current chain.",
"explore.uniVolume": "Uniswap volume",
"extension.announcement": "The Uniswap Extension is here. Swap, sign transactions, and send tokens right from your browser.",
"extension.announcement": "The Uniswap Extension Beta is here. Swap, sign transactions, and send tokens right from your sidebar.",
"extension.introduction": "Introducing the Uniswap Extension.",
"extension.open": "Open Uniswap Extension",
"fee.bestForExotic": "Best for exotic pairs.",
......
......@@ -16,6 +16,8 @@ import { UniverseChainId } from 'uniswap/src/types/chains'
const Container = styled(Box)`
min-width: 100%;
min-height: 100vh;
height: min-content;
padding-top: ${({ theme }) => theme.navHeight}px;
`
const LandingSwapContainer = styled(Box)`
......@@ -104,7 +106,6 @@ export function Hero({ scrollToRef, transition }: HeroProps) {
return (
<Container
position="relative"
height="100vh"
justify="center"
style={{ transform: `translate(0px, ${translateY}px)`, opacity: opacityY }}
>
......
......@@ -19,7 +19,7 @@ import { BottomSheetModalProps } from 'uniswap/src/components/modals/BottomSheet
import { HandleBar } from 'uniswap/src/components/modals/HandleBar'
import Trace from 'uniswap/src/features/telemetry/Trace'
import { useKeyboardLayout } from 'uniswap/src/utils/useKeyboardLayout'
import { isIOS } from 'utilities/src/platform'
import { isAndroid, isIOS } from 'utilities/src/platform'
/**
* (android only)
......@@ -250,6 +250,8 @@ function BottomSheetModalContents({
{...background}
{...backdrop}
ref={modalRef}
// Adds vertical pan gesture activate offset to avoid nested scroll gesture handler conflicts on android
activeOffsetY={isAndroid ? 12 : undefined}
animatedPosition={animatedPosition}
backgroundStyle={backgroundStyle}
containerComponent={containerComponent}
......
......@@ -18,6 +18,7 @@ export const ServiceProviderLogoStyles = {
icon: {
height: SERVICE_PROVIDER_ICON_SIZE,
width: SERVICE_PROVIDER_ICON_SIZE,
borderRadius: SERVICE_PROVIDER_ICON_BORDER_RADIUS,
},
uniswapLogoWrapper: {
backgroundColor: '#FFEFF8', // #FFD8EF with 40% opacity on a white background
......
......@@ -6,9 +6,8 @@ export enum DynamicConfigs {
// Wallet
MobileForceUpgrade = 'force_upgrade',
OnDeviceRecovery = 'on_device_recovery',
PollingIntervals = 'polling_intervals',
Slippage = 'slippage_configs',
UwuLink = 'uwulink_config',
Swap = 'swap_config',
// Web
QuickRouteChains = 'quick_route_chains',
......@@ -26,13 +25,10 @@ export enum OnDeviceRecoveryConfigKey {
MaxMnemonicsToLoad = 'maxMnemonicsToLoad',
}
export enum PollingIntervalsConfigKey {
export enum SwapConfigKey {
AverageL1BlockTimeMs = 'averageL1BlockTimeMs',
AverageL2BlockTimeMs = 'averageL2BlockTimeMs',
TradingApiSwapRequestMs = 'tradingApiSwapRequestMs',
}
export enum SlippageConfigKey {
MinAutoSlippageToleranceL2 = 'minAutoSlippageToleranceL2',
}
......@@ -49,9 +45,8 @@ export type DynamicConfigKeys = {
// Wallet
[DynamicConfigs.MobileForceUpgrade]: ForceUpgradeConfigKey
[DynamicConfigs.OnDeviceRecovery]: OnDeviceRecoveryConfigKey
[DynamicConfigs.PollingIntervals]: PollingIntervalsConfigKey
[DynamicConfigs.Slippage]: SlippageConfigKey
[DynamicConfigs.UwuLink]: UwuLinkConfigKey
[DynamicConfigs.Swap]: SwapConfigKey
// Web
[DynamicConfigs.QuickRouteChains]: QuickRouteChainsConfigKey
......
......@@ -4,6 +4,8 @@
export enum MobileEventName {
AppRating = 'App Rating',
AutomatedOnDeviceRecoveryTriggered = 'Automated On Device Recovery Triggered',
AutomatedOnDeviceRecoveryMnemonicsFound = 'Automated On Device Recovery Mnemonics Found',
AutomatedOnDeviceRecoverySingleMnemonicFetched = 'Automated On Device Recovery Mnemonic Fetched',
BalancesReport = 'Balances Report',
DeepLinkOpened = 'Deep Link Opened',
ExploreFilterSelected = 'Explore Filter Selected',
......
......@@ -135,6 +135,10 @@ export const ElementName = {
OK: 'ok',
OnboardingImportBackup: 'onboarding-import-backup',
OnboardingImportSeedPhrase: 'onboarding-import-seed-phrase',
OnDeviceRecoveryImportOther: 'on-device-recovery-import-other',
OnDeviceRecoveryWallet: 'on-device-recovery-wallet',
OnDeviceRecoveryModalCancel: 'on-device-recovery-modal-cancel',
OnDeviceRecoveryModalConfirm: 'on-device-recovery-modal-confirm',
OpenCameraRoll: 'open-camera-roll',
OpenNftsList: 'open-nfts-list',
QRCodeModalToggle: 'qr-code-modal-toggle',
......
......@@ -382,6 +382,14 @@ export type UniverseEventProperties = {
isBiometricsEnrolled: boolean | undefined
isBiometricAuthEnabled: boolean
}
[MobileEventName.AutomatedOnDeviceRecoveryMnemonicsFound]: {
mnemonicCount: number
}
[MobileEventName.AutomatedOnDeviceRecoverySingleMnemonicFetched]: {
balance: number
hasUnitag: boolean
hasENS: boolean
}
[MobileEventName.BalancesReport]: {
total_balances_usd: number
wallets: string[]
......
......@@ -792,8 +792,11 @@
"swap.warning.insufficientGas.title": "You don’t have enough {{currencySymbol}} to cover the network cost",
"swap.warning.lowLiquidity.message": "There isn’t currently enough liquidity available between these tokens to perform a swap. Please try again later or select another token.",
"swap.warning.lowLiquidity.title": "Not enough liquidity",
"swap.warning.networkFee.allow": "Allow {{ inputTokenSymbol }} (one time)",
"swap.warning.networkFee.highRelativeToValue": "The network cost exceeds 10% of your total transaction value.",
"swap.warning.networkFee.message": "This is the cost to process your transaction on the blockchain. Uniswap does not receive any share of these fees.",
"swap.warning.networkFee.message.uniswapX": "This is the cost to process your transaction on the blockchain. Uniswap does not receive any share of these fees. <gradient>UniswapX</gradient> aggregates liquidity sources for better prices and gas free swaps.",
"swap.warning.networkFee.wrap": "Wrap ETH",
"swap.warning.offline.message": "You may have lost internet connection or the network may be down. Please check your internet connection and try again.",
"swap.warning.offline.title": "You’re offline",
"swap.warning.priceImpact.message": "Due to the amount of {{outputCurrencySymbol}} liquidity currently available, the more {{inputCurrencySymbol}} you try to swap, the less {{outputCurrencySymbol}} you will receive.",
......@@ -992,7 +995,7 @@
"transaction.warning.insufficientGas.modal.title.withoutNetwork": "Not enough {{tokenSymbol}}",
"transaction.watcher.error.cancel": "Unable to cancel transaction",
"transaction.watcher.error.status": "Error while checking transaction status",
"uniswapx.description": "UniswapX aggregates liquidity sources for better prices and gas free swaps.",
"uniswapx.description": "<gradient>UniswapX</gradient> aggregates liquidity sources for better prices and gas free swaps.",
"uniswapx.included": "Includes <icon /> <gradient>UniswapX</gradient> <info />",
"uniswapx.label": "UniswapX",
"unitags.banner.button.claim": "Claim now",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -15,7 +15,7 @@ export type WarningModalProps = {
onConfirm?: () => void
modalName: ModalNameType
title: ReactNode
caption?: string
caption?: ReactNode
closeText?: string
confirmText?: string
severity?: WarningSeverity
......@@ -89,7 +89,7 @@ export function WarningModal({
{title}
</Text>
{caption && (
<Text color="$neutral2" textAlign="center" variant={isWeb ? 'body3' : 'body2'}>
<Text color="$neutral2" textAlign="center" variant="body3">
{caption}
</Text>
)}
......
......@@ -32,7 +32,9 @@ export function WarningTooltip({
<Text color="$neutral2" variant="body3">
{text}
</Text>
<Flex alignSelf="flex-start">{button}</Flex>
<Flex alignSelf="flex-start" width="100%">
{button}
</Flex>
</Flex>
</Flex>
<Tooltip.Arrow />
......
......@@ -3,7 +3,7 @@ import { PopperProps } from 'ui/src'
export type WarningTooltipProps = {
title?: string
text: string
text: ReactNode
icon?: Maybe<JSX.Element>
button: ReactNode
trigger: ReactNode
......
......@@ -6,21 +6,23 @@ import { iconSizes } from 'ui/src/theme'
import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo'
import { WalletChainId } from 'uniswap/src/types/chains'
import { NumberType } from 'utilities/src/format/types'
import { useFormattedUniswapXGasFeeInfo } from 'wallet/src/components/network/hooks'
import { useUSDValue } from 'wallet/src/features/gas/hooks'
import { GasFeeResult } from 'wallet/src/features/gas/types'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { useGasFeeHighRelativeToValue } from 'wallet/src/features/transactions/swap/hooks/useGasFeeHighRelativeToValue'
import { NetworkFeeWarning } from 'wallet/src/features/transactions/swap/modals/NetworkFeeWarning'
import { UniswapXGasBreakdown } from 'wallet/src/features/transactions/swap/trade/api/hooks/useSwapTxAndGasInfo'
export function NetworkFee({
chainId,
gasFee,
preUniswapXGasFeeUSD,
uniswapXGasBreakdown,
transactionUSDValue,
}: {
chainId: WalletChainId
gasFee: GasFeeResult
preUniswapXGasFeeUSD?: number
uniswapXGasBreakdown?: UniswapXGasBreakdown
transactionUSDValue?: Maybe<CurrencyAmount<Currency>>
}): JSX.Element {
const { t } = useTranslation()
......@@ -28,28 +30,29 @@ export function NetworkFee({
const gasFeeUSD = useUSDValue(chainId, gasFee.value ?? undefined)
const gasFeeFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice)
const preSavingsGasFeeFormatted = convertFiatAmountFormatted(preUniswapXGasFeeUSD, NumberType.FiatGasPrice)
const uniswapXGasFeeInfo = useFormattedUniswapXGasFeeInfo(uniswapXGasBreakdown, chainId)
const gasFeeHighRelativeToValue = useGasFeeHighRelativeToValue(gasFeeUSD, transactionUSDValue)
const isLoading = gasFee.loading
return (
<Flex row alignItems="center" gap="$spacing12" justifyContent="space-between">
<NetworkFeeWarning gasFeeHighRelativeToValue={gasFeeHighRelativeToValue}>
<NetworkFeeWarning gasFeeHighRelativeToValue={gasFeeHighRelativeToValue} uniswapXGasFeeInfo={uniswapXGasFeeInfo}>
<Text color="$neutral2" flexShrink={1} numberOfLines={3} variant="body3">
{t('transaction.networkCost.label')}
</Text>
</NetworkFeeWarning>
<Flex row alignItems="center" gap={preUniswapXGasFeeUSD ? '$spacing4' : '$spacing8'}>
{(!preUniswapXGasFeeUSD || gasFee.error) && (
<Flex row alignItems="center" gap={uniswapXGasBreakdown ? '$spacing4' : '$spacing8'}>
{(!uniswapXGasBreakdown || gasFee.error) && (
<NetworkLogo chainId={chainId} shape="square" size={iconSizes.icon16} />
)}
{gasFee.error ? (
<Text color="$neutral2" variant="body3">
{t('common.text.notAvailable')}
</Text>
) : preUniswapXGasFeeUSD ? (
<UniswapXFee gasFee={gasFeeFormatted} preSavingsGasFee={preSavingsGasFeeFormatted} />
) : uniswapXGasBreakdown ? (
<UniswapXFee gasFee={gasFeeFormatted} preSavingsGasFee={uniswapXGasFeeInfo?.preSavingsGasFeeFormatted} />
) : (
<Text
color={isLoading ? '$neutral3' : gasFeeHighRelativeToValue ? '$statusCritical' : '$neutral1'}
......
import { useMemo } from 'react'
import { WalletChainId } from 'uniswap/src/types/chains'
import { NumberType } from 'utilities/src/format/types'
import { useUSDValue } from 'wallet/src/features/gas/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { UniswapXGasBreakdown } from 'wallet/src/features/transactions/swap/trade/api/hooks/useSwapTxAndGasInfo'
export type FormattedUniswapXGasFeeInfo = {
approvalFeeFormatted?: string
wrapFeeFormatted?: string
swapFeeFormatted: string
preSavingsGasFeeFormatted: string
inputTokenSymbol?: string
}
export function useFormattedUniswapXGasFeeInfo(
uniswapXGasBreakdown: UniswapXGasBreakdown | undefined,
chainId: WalletChainId,
): FormattedUniswapXGasFeeInfo | undefined {
const { convertFiatAmountFormatted } = useLocalizationContext()
const approvalCostUsd = useUSDValue(chainId, uniswapXGasBreakdown?.approvalCost)
const wrapCostUsd = useUSDValue(chainId, uniswapXGasBreakdown?.wrapCost)
return useMemo(() => {
if (!uniswapXGasBreakdown) {
return undefined
}
const { approvalCost, wrapCost, inputTokenSymbol } = uniswapXGasBreakdown
// Without uniswapx, the swap would have costed approval price + classic swap fee. A separate wrap tx would not have occurred.
const preSavingsGasCostUsd =
Number(approvalCostUsd ?? 0) + Number(uniswapXGasBreakdown?.classicGasUseEstimateUSD ?? 0)
const preSavingsGasFeeFormatted = convertFiatAmountFormatted(preSavingsGasCostUsd, NumberType.FiatGasPrice)
// Swap submission will always cost 0, since it's not an on-chain tx.
const swapFeeFormatted = convertFiatAmountFormatted(0, NumberType.FiatGasPrice)
return {
approvalFeeFormatted: approvalCost
? convertFiatAmountFormatted(approvalCostUsd, NumberType.FiatGasPrice)
: undefined,
wrapFeeFormatted: wrapCost ? convertFiatAmountFormatted(wrapCostUsd, NumberType.FiatGasPrice) : undefined,
preSavingsGasFeeFormatted,
swapFeeFormatted,
inputTokenSymbol,
}
}, [uniswapXGasBreakdown, approvalCostUsd, convertFiatAmountFormatted, wrapCostUsd])
}
......@@ -35,7 +35,6 @@ type ActivityDataProps = {
type ActivityData = {
maybeEmptyComponent: JSX.Element | null
maybeLoaderComponent: JSX.Element | null
renderActivityItem: ActivityItemRenderer
sectionData: (TransactionDetails | SectionHeader | LoadingItem)[] | undefined
keyExtractor: (item: TransactionDetails | SectionHeader | LoadingItem) => string
......@@ -79,7 +78,7 @@ export function useActivityData({
)
}, [swapCallbacks, authTrigger])
const { onRetry, hasData, isLoading, isError, sectionData, keyExtractor } = useFormattedTransactionDataForActivity(
const { onRetry, isError, sectionData, keyExtractor } = useFormattedTransactionDataForActivity(
owner,
hideSpamTokens,
useMergeLocalAndRemoteTransactions,
......@@ -111,13 +110,11 @@ export function useActivityData({
</Flex>
)
const maybeEmptyComponent = hasData ? null : isError ? errorCard : emptyListView
// We want to display the loading shimmer only on first load because items have their own loading shimmer
const maybeLoaderComponent = isLoading && !hasData ? <Loader.Transaction repeat={6} /> : null
// We check `sectionData` instead of `hasData` because `sectionData` has either transactions or a loading skeleton.
const maybeEmptyComponent = sectionData?.length ? null : isError ? errorCard : emptyListView
return {
maybeEmptyComponent,
maybeLoaderComponent,
renderActivityItem,
sectionData,
keyExtractor,
......
import { call, put, takeLatest } from 'typed-redux-saga'
import { put, takeLatest } from 'typed-redux-saga'
import { AssetType } from 'uniswap/src/entities/assets'
import { WalletChainId } from 'uniswap/src/types/chains'
import { WalletConnectEvent } from 'uniswap/src/types/walletConnect'
import { buildReceiveNotification } from 'wallet/src/features/notifications/buildReceiveNotification'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import { getAmountsFromTrade } from 'wallet/src/features/transactions/getAmountsFromTrade'
import { selectTransactions } from 'wallet/src/features/transactions/selectors'
import { finalizeTransaction } from 'wallet/src/features/transactions/slice'
import { TransactionType } from 'wallet/src/features/transactions/types'
import { appSelect } from 'wallet/src/state'
import { TransactionDetails, TransactionType } from 'wallet/src/features/transactions/types'
export function* notificationWatcher() {
yield* takeLatest(finalizeTransaction.type, pushTransactionNotification)
}
export function* pushTransactionNotification(action: ReturnType<typeof finalizeTransaction>) {
const { chainId, status, typeInfo, id, from, addedTime } = action.payload
if (shouldSuppressNotification(action.payload)) {
return
}
const { chainId, status, typeInfo, id, from } = action.payload
const baseNotificationData = {
txStatus: status,
......@@ -26,18 +27,15 @@ export function* pushTransactionNotification(action: ReturnType<typeof finalizeT
}
if (typeInfo.type === TransactionType.Approve) {
const shouldSuppressNotification = yield* call(suppressApproveNotification, from, chainId, addedTime)
if (!shouldSuppressNotification) {
yield* put(
pushNotification({
...baseNotificationData,
type: AppNotificationType.Transaction,
txType: TransactionType.Approve,
tokenAddress: typeInfo.tokenAddress,
spender: typeInfo.spender,
}),
)
}
yield* put(
pushNotification({
...baseNotificationData,
type: AppNotificationType.Transaction,
txType: TransactionType.Approve,
tokenAddress: typeInfo.tokenAddress,
spender: typeInfo.spender,
}),
)
} else if (typeInfo.type === TransactionType.Swap) {
const { inputCurrencyAmountRaw, outputCurrencyAmountRaw } = getAmountsFromTrade(typeInfo)
yield* put(
......@@ -118,20 +116,9 @@ export function* pushTransactionNotification(action: ReturnType<typeof finalizeT
}
}
// If an approve tx is submitted with a swap tx (i.e, swap tx is added within 3 seconds of an approve tx),
// then suppress the approve notification
function* suppressApproveNotification(address: Address, chainId: WalletChainId, approveAddedTime: number) {
const transactions = (yield* appSelect(selectTransactions))?.[address]?.[chainId]
const transactionDetails = Object.values(transactions ?? {})
const foundSwapTx = transactionDetails.find((tx) => {
const { type } = tx.typeInfo
if (type !== TransactionType.Swap) {
return false
}
const swapAddedTime = tx.addedTime
return swapAddedTime - approveAddedTime < 3000
})
return !!foundSwapTx
// If a wrap or approve tx is submitted with a swap, then suppress the notification.
function shouldSuppressNotification(tx: TransactionDetails) {
return (
(tx.typeInfo.type === TransactionType.Approve || tx.typeInfo.type === TransactionType.Wrap) && tx.typeInfo.swapTxId
)
}
......@@ -18,13 +18,14 @@ import {
} from 'wallet/src/features/transactions/TransactionDetails/FeeOnTransferFee'
import { SwapFee } from 'wallet/src/features/transactions/TransactionDetails/SwapFee'
import { Warning } from 'wallet/src/features/transactions/WarningModal/types'
import { UniswapXGasBreakdown } from 'wallet/src/features/transactions/swap/trade/api/hooks/useSwapTxAndGasInfo'
import { SwapFeeInfo } from 'wallet/src/features/transactions/swap/trade/types'
interface TransactionDetailsProps {
banner?: ReactNode
chainId: WalletChainId
gasFee: GasFeeResult
preUniswapXGasFeeUSD?: number
uniswapXGasBreakdown?: UniswapXGasBreakdown
showExpandedChildren?: boolean
swapFeeInfo?: SwapFeeInfo
showWarning?: boolean
......@@ -42,7 +43,7 @@ export function TransactionDetails({
showExpandedChildren,
chainId,
gasFee,
preUniswapXGasFeeUSD,
uniswapXGasBreakdown,
swapFeeInfo,
showWarning,
warning,
......@@ -125,8 +126,8 @@ export function TransactionDetails({
<NetworkFee
chainId={chainId}
gasFee={gasFee}
preUniswapXGasFeeUSD={preUniswapXGasFeeUSD}
transactionUSDValue={transactionUSDValue}
uniswapXGasBreakdown={uniswapXGasBreakdown}
/>
{AccountDetails}
</Flex>
......
import { useTranslation } from 'react-i18next'
import { Flex, Text, Tooltip } from 'ui/src'
import { Flex, SpaceTokens, Text, Tooltip } from 'ui/src'
import { AlertTriangle } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { areAddressesEqual } from 'uniswap/src/utils/addresses'
......@@ -10,9 +10,11 @@ import { ContentRow } from 'wallet/src/features/transactions/TransactionRequest/
export function AddressFooter({
connectedAccountAddress,
activeAccountAddress,
px = '$none',
}: {
connectedAccountAddress?: string
activeAccountAddress: string
px?: SpaceTokens
}): JSX.Element {
const { t } = useTranslation()
......@@ -23,7 +25,7 @@ export function AddressFooter({
const showWarning = connectedAccountAddress && !areAddressesEqual(connectedAccountAddress, activeAccountAddress)
return (
<Flex grow px="$spacing8">
<Flex grow px={px}>
<ContentRow
label={
<Flex grow row alignItems="center" gap="$spacing4">
......
/* eslint-disable complexity */
import { useCallback, useState } from 'react'
import { Keyboard } from 'react-native'
import { FadeIn, FadeOut } from 'react-native-reanimated'
......@@ -9,6 +8,7 @@ import { iconSizes } from 'ui/src/theme'
import { CurrencyField } from 'uniswap/src/features/transactions/transactionState/types'
import { NumberType } from 'utilities/src/format/types'
import { UniswapXFee } from 'wallet/src/components/network/NetworkFee'
import { useFormattedUniswapXGasFeeInfo } from 'wallet/src/components/network/hooks'
import { useUSDValue } from 'wallet/src/features/gas/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { InsufficientNativeTokenWarning } from 'wallet/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning'
......@@ -19,7 +19,6 @@ import { GasAndWarningRowsProps } from 'wallet/src/features/transactions/swap/Ga
import { SwapWarningModal } from 'wallet/src/features/transactions/swap/SwapWarningModal'
import { useGasFeeHighRelativeToValue } from 'wallet/src/features/transactions/swap/hooks/useGasFeeHighRelativeToValue'
import { NetworkFeeWarning } from 'wallet/src/features/transactions/swap/modals/NetworkFeeWarning'
import { UniswapXInfo } from 'wallet/src/features/transactions/swap/modals/UniswapXInfo'
import { isUniswapX } from 'wallet/src/features/transactions/swap/trade/utils'
import { BlockedAddressWarning } from 'wallet/src/features/trm/BlockedAddressWarning'
import { useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks'
......@@ -29,7 +28,8 @@ export function GasAndWarningRows({ renderEmptyRows }: GasAndWarningRowsProps):
const isShortMobileDevice = useIsShortMobileDevice()
const { convertFiatAmountFormatted } = useLocalizationContext()
const { gasFee, trade } = useSwapTxContext()
const swapTxContext = useSwapTxContext()
const { gasFee } = swapTxContext
const { derivedSwapInfo } = useSwapFormContext()
const { chainId, currencyAmountsUSDValue } = derivedSwapInfo
......@@ -45,14 +45,13 @@ export function GasAndWarningRows({ renderEmptyRows }: GasAndWarningRowsProps):
const gasFeeUSD = useUSDValue(chainId, gasFee?.value)
const gasFeeFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice)
const showUniswapXFee = Boolean(gasFeeUSD && trade && isUniswapX(trade))
const preSavingsGasFeeFormatted =
trade && isUniswapX(trade)
? convertFiatAmountFormatted(trade.quote.quote.classicGasUseEstimateUSD, NumberType.FiatGasPrice)
: undefined
const uniswapXGasFeeInfo = useFormattedUniswapXGasFeeInfo(
isUniswapX(swapTxContext) ? swapTxContext.gasFeeBreakdown : undefined,
chainId,
)
// only show the gas fee icon and price if we have a valid fee
const showGasFee = Boolean(gasFeeUSD && !showUniswapXFee)
const showGasFee = Boolean(gasFeeUSD)
const onSwapWarningClick = useCallback(() => {
if (!formScreenWarning?.warning.message) {
......@@ -94,21 +93,26 @@ export function GasAndWarningRows({ renderEmptyRows }: GasAndWarningRowsProps):
)}
<Flex centered row>
{showUniswapXFee && (
<UniswapXInfo tooltipTrigger={<></>}>
<AnimatedFlex centered row entering={FadeIn} gap="$spacing4">
<UniswapXFee gasFee={gasFeeFormatted} preSavingsGasFee={preSavingsGasFeeFormatted} />
</AnimatedFlex>
</UniswapXInfo>
)}
{showGasFee && (
<NetworkFeeWarning gasFeeHighRelativeToValue={gasFeeHighRelativeToValue} tooltipTrigger={<></>}>
<NetworkFeeWarning
gasFeeHighRelativeToValue={gasFeeHighRelativeToValue}
tooltipTrigger={null}
uniswapXGasFeeInfo={uniswapXGasFeeInfo}
>
<AnimatedFlex centered row entering={FadeIn} gap="$spacing4">
<Gas color={gasColor} size="$icon.16" />
<Text color={gasColor} variant="body3">
{gasFeeFormatted}
</Text>
{uniswapXGasFeeInfo ? (
<UniswapXFee
gasFee={gasFeeFormatted}
preSavingsGasFee={uniswapXGasFeeInfo.preSavingsGasFeeFormatted}
/>
) : (
<>
<Gas color={gasColor} size="$icon.16" />
<Text color={gasColor} variant="body3">
{gasFeeFormatted}
</Text>
</>
)}
</AnimatedFlex>
</NetworkFeeWarning>
)}
......
......@@ -10,6 +10,7 @@ import { CurrencyField } from 'uniswap/src/features/transactions/transactionStat
import { normalizePriceImpact } from 'utilities/src/format/normalizePriceImpact'
import { NumberType } from 'utilities/src/format/types'
import { UniswapXFee } from 'wallet/src/components/network/NetworkFee'
import { useFormattedUniswapXGasFeeInfo } from 'wallet/src/components/network/hooks'
import { useUSDValue } from 'wallet/src/features/gas/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { InsufficientNativeTokenWarning } from 'wallet/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning'
......@@ -22,19 +23,18 @@ import { SwapWarningModal } from 'wallet/src/features/transactions/swap/SwapWarn
import { useGasFeeHighRelativeToValue } from 'wallet/src/features/transactions/swap/hooks/useGasFeeHighRelativeToValue'
import { NetworkFeeWarning } from 'wallet/src/features/transactions/swap/modals/NetworkFeeWarning'
import { PriceImpactWarning } from 'wallet/src/features/transactions/swap/modals/PriceImpactWarning'
import { UniswapXInfo } from 'wallet/src/features/transactions/swap/modals/UniswapXInfo'
import { isUniswapX } from 'wallet/src/features/transactions/swap/trade/utils'
import { BlockedAddressWarning } from 'wallet/src/features/trm/BlockedAddressWarning'
import { useIsBlockedActiveAddress } from 'wallet/src/features/trm/hooks'
// eslint-disable-next-line complexity
export function GasAndWarningRows({
renderEmptyRows: _renderEmptyRows, // Web does not need to render empty rows for layout calculations
}: GasAndWarningRowsProps): JSX.Element {
const { convertFiatAmountFormatted, formatPercent } = useLocalizationContext()
const { t } = useTranslation()
const { gasFee } = useSwapTxContext()
const swapTxContext = useSwapTxContext()
const { gasFee } = swapTxContext
const { derivedSwapInfo } = useSwapFormContext()
const { chainId, trade, currencyAmountsUSDValue } = derivedSwapInfo
......@@ -52,13 +52,12 @@ export function GasAndWarningRows({
const gasFeeUSD = useUSDValue(chainId, gasFee?.value)
const gasFeeFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice)
const showUniswapXFee = Boolean(gasFeeUSD && trade.trade && isUniswapX(trade.trade))
const preSavingsGasFeeFormatted =
trade.trade && isUniswapX(trade.trade)
? convertFiatAmountFormatted(trade.trade.quote.quote.classicGasUseEstimateUSD, NumberType.FiatGasPrice)
: undefined
const uniswapXGasFeeInfo = useFormattedUniswapXGasFeeInfo(
isUniswapX(swapTxContext) ? swapTxContext.gasFeeBreakdown : undefined,
chainId,
)
const showGasFee = Boolean(gasFeeUSD && !showUniswapXFee)
const showGasFee = Boolean(gasFeeUSD)
const onSwapWarningClick = useCallback(() => {
if (!formScreenWarning?.warning.message) {
......@@ -106,29 +105,28 @@ export function GasAndWarningRows({
</Flex>
)}
{showUniswapXFee && (
<UniswapXInfo
placement="bottom"
tooltipTrigger={
<AnimatedFlex centered row entering={FadeIn} gap="$spacing4">
<UniswapXFee gasFee={gasFeeFormatted} preSavingsGasFee={preSavingsGasFeeFormatted} />
</AnimatedFlex>
}
/>
)}
{showGasFee && (
<NetworkFeeWarning
gasFeeHighRelativeToValue={gasFeeHighRelativeToSwapValue}
placement="bottom"
tooltipTrigger={
<AnimatedFlex centered row entering={FadeIn} gap="$spacing4">
<Gas color={gasFeeHighRelativeToSwapValue ? '$statusCritical' : '$neutral2'} size="$icon.16" />
<Text color={gasFeeHighRelativeToSwapValue ? '$statusCritical' : '$neutral2'} variant="body4">
{gasFeeFormatted}
</Text>
{uniswapXGasFeeInfo ? (
<UniswapXFee
gasFee={gasFeeFormatted}
preSavingsGasFee={uniswapXGasFeeInfo.preSavingsGasFeeFormatted}
/>
) : (
<>
<Gas color={gasFeeHighRelativeToSwapValue ? '$statusCritical' : '$neutral2'} size="$icon.16" />
<Text color={gasFeeHighRelativeToSwapValue ? '$statusCritical' : '$neutral2'} variant="body4">
{gasFeeFormatted}
</Text>
</>
)}
</AnimatedFlex>
}
uniswapXGasFeeInfo={uniswapXGasFeeInfo}
/>
)}
</Flex>
......
......@@ -15,8 +15,8 @@ import { FeeOnTransferFeeGroupProps } from 'wallet/src/features/transactions/Tra
import { TransactionDetails } from 'wallet/src/features/transactions/TransactionDetails/TransactionDetails'
import { Warning } from 'wallet/src/features/transactions/WarningModal/types'
import { SwapRateRatio } from 'wallet/src/features/transactions/swap/SwapRateRatio'
import { UniswapXGasBreakdown } from 'wallet/src/features/transactions/swap/trade/api/hooks/useSwapTxAndGasInfo'
import { Trade } from 'wallet/src/features/transactions/swap/trade/types'
import { isUniswapX } from 'wallet/src/features/transactions/swap/trade/utils'
import { DerivedSwapInfo } from 'wallet/src/features/transactions/swap/types'
import { getFormattedCurrencyAmount } from 'wallet/src/utils/currency'
import { ValueType, getCurrencyAmount } from 'wallet/src/utils/getCurrencyAmount'
......@@ -50,6 +50,7 @@ interface SwapDetailsProps {
derivedSwapInfo: DerivedSwapInfo<CurrencyInfo, CurrencyInfo>
gasFallbackUsed?: boolean
gasFee: GasFeeResult
uniswapXGasBreakdown?: UniswapXGasBreakdown
newTradeRequiresAcceptance: boolean
outputCurrencyPricePerUnitExact?: string
warning?: Warning
......@@ -64,6 +65,7 @@ export function SwapDetails({
customSlippageTolerance,
derivedSwapInfo,
gasFee,
uniswapXGasBreakdown,
newTradeRequiresAcceptance,
outputCurrencyPricePerUnitExact,
warning,
......@@ -123,8 +125,6 @@ export function SwapDetails({
],
)
const preUniswapXGasFeeUSD = isUniswapX(trade) ? trade.quote.quote.classicGasUseEstimateUSD : undefined
return (
<TransactionDetails
isSwap
......@@ -140,11 +140,11 @@ export function SwapDetails({
chainId={acceptedTrade.inputAmount.currency.chainId}
feeOnTransferProps={feeOnTransferProps}
gasFee={gasFee}
preUniswapXGasFeeUSD={preUniswapXGasFeeUSD}
showExpandedChildren={!!customSlippageTolerance}
showWarning={warning && !newTradeRequiresAcceptance}
swapFeeInfo={swapFeeInfo}
transactionUSDValue={derivedSwapInfo.currencyAmountsUSDValue[CurrencyField.OUTPUT]}
uniswapXGasBreakdown={uniswapXGasBreakdown}
warning={warning}
onShowWarning={onShowWarning}
>
......
......@@ -58,6 +58,7 @@ export function SwapReviewScreen({ hideContent }: { hideContent: boolean }): JSX
const swapTxContext = useSwapTxContext()
const { gasFee, trade } = swapTxContext
const uniswapXGasBreakdown = isUniswapX(swapTxContext) ? swapTxContext.gasFeeBreakdown : undefined
const {
derivedSwapInfo,
......@@ -406,6 +407,7 @@ export function SwapReviewScreen({ hideContent }: { hideContent: boolean }): JSX
gasFee={gasFee}
newTradeRequiresAcceptance={newTradeRequiresAcceptance}
outputCurrencyPricePerUnitExact={outputCurrencyPricePerUnitExact}
uniswapXGasBreakdown={uniswapXGasBreakdown}
warning={reviewScreenWarning?.warning}
onAcceptTrade={onAcceptTrade}
onShowSlippageModal={onShowSlippageModal}
......
......@@ -6,10 +6,8 @@ import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo'
import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'
import { CurrencyField } from 'uniswap/src/features/transactions/transactionState/types'
import { getSymbolDisplayText } from 'uniswap/src/utils/currency'
import { buildCurrencyId, currencyAddress } from 'uniswap/src/utils/currencyId'
import { NumberType } from 'utilities/src/format/types'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo'
import { DerivedSwapInfo } from 'wallet/src/features/transactions/swap/types'
export function TransactionAmountsReview({
......@@ -23,45 +21,55 @@ export function TransactionAmountsReview({
}): JSX.Element {
const { t } = useTranslation()
const colors = useSporeColors()
const { convertFiatAmountFormatted, formatCurrencyAmount } = useLocalizationContext()
const { currencyAmountsUSDValue, exactCurrencyField, trade } = acceptedDerivedSwapInfo
const { convertFiatAmountFormatted, formatCurrencyAmount, formatNumberOrString } = useLocalizationContext()
if (!trade.trade) {
throw new Error('Missing required `trade` to render `TransactionAmountsReview`')
}
const { currencies, currencyAmounts, currencyAmountsUSDValue, exactAmountToken, exactCurrencyField } =
acceptedDerivedSwapInfo
const currencyInInfo = currencies[CurrencyField.INPUT]
const currencyOutInfo = currencies[CurrencyField.OUTPUT]
const usdAmountIn =
exactCurrencyField === CurrencyField.INPUT
? currencyAmountsUSDValue[CurrencyField.INPUT]?.toExact()
: acceptedDerivedSwapInfo?.currencyAmountsUSDValue[CurrencyField.INPUT]?.toExact()
const usdAmountOut =
exactCurrencyField === CurrencyField.OUTPUT
? currencyAmountsUSDValue[CurrencyField.OUTPUT]?.toExact()
: acceptedDerivedSwapInfo?.currencyAmountsUSDValue[CurrencyField.OUTPUT]?.toExact()
const formattedFiatAmountIn = convertFiatAmountFormatted(usdAmountIn, NumberType.FiatTokenQuantity)
const formattedFiatAmountOut = convertFiatAmountFormatted(usdAmountOut, NumberType.FiatTokenQuantity)
// Token amounts
// On review screen, always show values directly from trade object, to match exactly what is submitted on chain
const inputCurrencyAmount = trade.trade.inputAmount
const outputCurrencyAmount = trade.trade.outputAmount
const derivedCurrencyField = exactCurrencyField === CurrencyField.INPUT ? CurrencyField.OUTPUT : CurrencyField.INPUT
const formattedTokenAmountIn = formatCurrencyAmount({
value: inputCurrencyAmount,
const derivedAmount = formatCurrencyAmount({
value: acceptedDerivedSwapInfo?.currencyAmounts[derivedCurrencyField],
type: NumberType.TokenTx,
})
const formattedTokenAmountOut = formatCurrencyAmount({
value: outputCurrencyAmount,
const formattedExactAmountToken = formatNumberOrString({
value: exactAmountToken,
type: NumberType.TokenTx,
})
// USD amounts
const usdAmountIn = currencyAmountsUSDValue[CurrencyField.INPUT]?.toExact()
const usdAmountOut = currencyAmountsUSDValue[CurrencyField.OUTPUT]?.toExact()
const formattedFiatAmountIn = convertFiatAmountFormatted(usdAmountIn, NumberType.FiatTokenQuantity)
const formattedFiatAmountOut = convertFiatAmountFormatted(usdAmountOut, NumberType.FiatTokenQuantity)
const [formattedTokenAmountIn, formattedTokenAmountOut] =
exactCurrencyField === CurrencyField.INPUT
? [formattedExactAmountToken, derivedAmount]
: [derivedAmount, formattedExactAmountToken]
const shouldDimInput = newTradeRequiresAcceptance && exactCurrencyField === CurrencyField.OUTPUT
const shouldDimOutput = newTradeRequiresAcceptance && exactCurrencyField === CurrencyField.INPUT
// Rebuild currency infos directly from trade object to ensure it matches what is submitted on chain
const currencyInInfo = useCurrencyInfo(
buildCurrencyId(inputCurrencyAmount.currency.chainId, currencyAddress(inputCurrencyAmount.currency)),
)
const currencyOutInfo = useCurrencyInfo(
buildCurrencyId(outputCurrencyAmount.currency.chainId, currencyAddress(outputCurrencyAmount.currency)),
)
if (!currencyInInfo || !currencyOutInfo) {
if (
!currencyInInfo ||
!currencyOutInfo ||
!currencyAmounts[CurrencyField.INPUT] ||
!currencyAmounts[CurrencyField.OUTPUT] ||
!acceptedDerivedSwapInfo.currencyAmounts[CurrencyField.INPUT] ||
!acceptedDerivedSwapInfo.currencyAmounts[CurrencyField.OUTPUT]
) {
// This should never happen. It's just to keep TS happy.
throw new Error('Missing required props in `derivedSwapInfo` to render `TransactionAmountsReview` screen.')
}
......
import { PropsWithChildren } from 'react'
import { useTranslation } from 'react-i18next'
import { isWeb, useSporeColors } from 'ui/src'
import { Trans, useTranslation } from 'react-i18next'
import { Flex, Separator, Text, UniswapXText, isWeb, useSporeColors } from 'ui/src'
import { Gas } from 'ui/src/components/icons'
import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { isMobile } from 'utilities/src/platform'
import { WarningInfo } from 'wallet/src/components/modals/WarningModal/WarningInfo'
import { WarningTooltipProps } from 'wallet/src/components/modals/WarningModal/WarningTooltipProps'
import { UniswapXFee } from 'wallet/src/components/network/NetworkFee'
import { FormattedUniswapXGasFeeInfo } from 'wallet/src/components/network/hooks'
import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types'
export function NetworkFeeWarning({
......@@ -14,39 +17,43 @@ export function NetworkFeeWarning({
children,
tooltipTrigger,
placement = 'top',
uniswapXGasFeeInfo,
}: PropsWithChildren<{
gasFeeHighRelativeToValue?: boolean
tooltipTrigger?: WarningTooltipProps['trigger']
placement?: WarningTooltipProps['placement']
uniswapXGasFeeInfo?: FormattedUniswapXGasFeeInfo
}>): JSX.Element {
const colors = useSporeColors()
const { t } = useTranslation()
const text = gasFeeHighRelativeToValue
? t('swap.warning.networkFee.highRelativeToValue')
: t('swap.warning.networkFee.message')
const showHighGasFeeUI = gasFeeHighRelativeToValue && !uniswapXGasFeeInfo
return (
<WarningInfo
infoButton={
<LearnMoreLink
textVariant={isWeb ? 'buttonLabel4' : undefined}
url={uniswapUrls.helpArticleUrls.networkFeeInfo}
/>
uniswapXGasFeeInfo ? (
<UniswapXFeeContent uniswapXGasFeeInfo={uniswapXGasFeeInfo} />
) : (
<LearnMoreLink
textVariant={isWeb ? 'buttonLabel4' : undefined}
url={uniswapUrls.helpArticleUrls.networkFeeInfo}
/>
)
}
modalProps={{
backgroundIconColor: colors.surface2.get(),
caption: text,
caption: <NetworkFeeText showHighGasFeeUI={showHighGasFeeUI} uniswapXGasFeeInfo={uniswapXGasFeeInfo} />,
closeText: t('common.button.close'),
icon: <Gas color={gasFeeHighRelativeToValue ? '$statusCritical' : '$neutral2'} size="$icon.24" />,
icon: <Gas color={showHighGasFeeUI ? '$statusCritical' : '$neutral2'} size="$icon.24" />,
modalName: ModalName.NetworkFeeInfo,
severity: WarningSeverity.None,
title: t('transaction.networkCost.label'),
}}
tooltipProps={{
text,
text: <NetworkFeeText showHighGasFeeUI={showHighGasFeeUI} uniswapXGasFeeInfo={uniswapXGasFeeInfo} />,
placement,
icon: gasFeeHighRelativeToValue ? <Gas color="$statusCritical" size="$icon.16" /> : null,
icon: showHighGasFeeUI ? <Gas color="$statusCritical" size="$icon.16" /> : null,
}}
trigger={tooltipTrigger}
>
......@@ -54,3 +61,65 @@ export function NetworkFeeWarning({
</WarningInfo>
)
}
function NetworkFeeText({
showHighGasFeeUI,
uniswapXGasFeeInfo,
}: {
showHighGasFeeUI?: boolean
uniswapXGasFeeInfo?: FormattedUniswapXGasFeeInfo
}): JSX.Element {
const { t } = useTranslation()
if (uniswapXGasFeeInfo) {
return (
<Trans
components={{ gradient: <UniswapXText height={18} variant="body3" /> }}
i18nKey="swap.warning.networkFee.message.uniswapX"
/>
)
} else if (showHighGasFeeUI) {
return t('swap.warning.networkFee.highRelativeToValue')
} else {
return t('swap.warning.networkFee.message')
}
}
function UniswapXFeeContent({ uniswapXGasFeeInfo }: { uniswapXGasFeeInfo: FormattedUniswapXGasFeeInfo }): JSX.Element {
const { approvalFeeFormatted, wrapFeeFormatted, swapFeeFormatted, inputTokenSymbol } = uniswapXGasFeeInfo
const { t } = useTranslation()
return (
<Flex gap="$spacing12">
<Flex centered={isMobile} width="100%">
<LearnMoreLink
textVariant={isWeb ? 'buttonLabel4' : undefined}
url={uniswapUrls.helpArticleUrls.uniswapXInfo}
/>
</Flex>
<Separator />
{wrapFeeFormatted && (
<Flex row justifyContent="space-between" width="100%">
<Text color="$neutral2" variant="body3">
{t('swap.warning.networkFee.wrap')}
</Text>
<Text variant="body3">{wrapFeeFormatted}</Text>
</Flex>
)}
{approvalFeeFormatted && (
<Flex row justifyContent="space-between" width="100%">
<Text color="$neutral2" variant="body3">
{t('swap.warning.networkFee.allow', { inputTokenSymbol })}
</Text>
<Text variant="body3">{approvalFeeFormatted}</Text>
</Flex>
)}
<Flex row justifyContent="space-between" width="100%">
<Text color="$neutral2" variant="body3">
{t('common.button.swap')}
</Text>
<UniswapXFee gasFee={swapFeeFormatted} />
</Flex>
</Flex>
)
}
......@@ -19,8 +19,14 @@ import { useCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo'
import { SwapTransactionDetails } from 'wallet/src/features/transactions/SummaryCards/DetailsModal/SwapTransactionDetails'
import { isSwapTransactionInfo } from 'wallet/src/features/transactions/SummaryCards/DetailsModal/types'
import { ErroredQueuedOrderStatus, useErroredQueuedOrders } from 'wallet/src/features/transactions/hooks'
import { useSelectAddressTransactions } from 'wallet/src/features/transactions/selectors'
import { updateTransaction } from 'wallet/src/features/transactions/slice'
import { QueuedOrderStatus, TransactionDetails, TransactionStatus } from 'wallet/src/features/transactions/types'
import {
QueuedOrderStatus,
TransactionDetails,
TransactionStatus,
TransactionType,
} from 'wallet/src/features/transactions/types'
import { useActiveSignerAccount } from 'wallet/src/features/wallet/hooks'
const QUEUE_STATUS_TO_MESSAGE = {
......@@ -56,15 +62,23 @@ export function QueuedOrderModal(): JSX.Element | null {
}
}, [transactionState, navigateToSwapFlow, onCancel])
const localTransactions = useSelectAddressTransactions(account?.address ?? null)
// If a wrap tx was involved as part of the order flow, show a message indicating that the user now has WETH,
// unless the wrap failed, in which case the user still has ETH and the message should not be shown.
const showWrapMessage = useMemo(() => {
if (!currentFailedOrder || currentFailedOrder?.queueStatus === QueuedOrderStatus.WrapFailed) {
return false
}
return localTransactions?.some(
(tx) => tx.typeInfo.type === TransactionType.Wrap && tx.typeInfo.swapTxId === currentFailedOrder?.id,
)
}, [localTransactions, currentFailedOrder])
// If there are no failed orders tracked in state, return nothing.
if (!uniswapXEnabled || !currentFailedOrder || !isSwapTransactionInfo(currentFailedOrder.typeInfo)) {
return null
}
const reason = QUEUE_STATUS_TO_MESSAGE[currentFailedOrder.queueStatus]
// If a wrap tx was involved as part of the order flow, show a message indicating that the user now has WETH,
// unless the wrap failed, in which case the user still has ETH and the message should not be shown.
const showWrapMessage =
Boolean(currentFailedOrder.wrapTxHash) && currentFailedOrder.queueStatus !== QueuedOrderStatus.WrapFailed
const buttonSize = isShortMobileDevice ? 'small' : 'medium'
......@@ -84,7 +98,7 @@ export function QueuedOrderModal(): JSX.Element | null {
<Text color="$neutral2" textAlign="center" variant="body3">
{reason}
{showWrapMessage && ' '}
{showWrapMessage && <> {t('swap.warning.queuedOrder.wrap.message')}</>}
{showWrapMessage && t('swap.warning.queuedOrder.wrap.message')}
</Text>
<LearnMoreLink
textColor="$neutral1"
......
import { PropsWithChildren } from 'react'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import { UniswapXText, isWeb } from 'ui/src'
import { UniswapX } from 'ui/src/components/icons'
import { colors, opacify } from 'ui/src/theme'
......@@ -30,7 +30,7 @@ export function UniswapXInfo({
}
modalProps={{
backgroundIconColor: opacify(16, colors.uniswapXPurple),
caption: t('uniswapx.description'),
caption: <Trans components={{ gradient: <></> }} i18nKey="uniswapx.description" />,
closeText: t('common.button.close'),
icon: <UniswapX size="$icon.24" />,
modalName: ModalName.UniswapXInfo,
......
......@@ -58,7 +58,6 @@ export function* submitUniswapXOrder(params: SubmitUniswapXOrderParams) {
addedTime: Date.now(),
status: TransactionStatus.Pending,
queueStatus: QueuedOrderStatus.Waiting,
wrapTxHash,
} satisfies UniswapXOrderDetails
yield* put(transactionActions.addTransaction(order))
......
......@@ -55,6 +55,7 @@ export function* approveAndSwap(params: SwapParams) {
type: TransactionType.Approve,
tokenAddress: approveTxRequest.to,
spender: permit2Address(chainId),
swapTxId: txId,
}
const options = { request: approveTxRequest, submitViaPrivateRpc }
......@@ -73,7 +74,12 @@ export function* approveAndSwap(params: SwapParams) {
// Wrap Logic - UniswapX Eth-input
if (wrapTxRequest) {
const inputCurrencyAmount = trade.inputAmount
const wrapResponse = yield* wrap({ txRequest: { ...wrapTxRequest, nonce }, account, inputCurrencyAmount })
const wrapResponse = yield* wrap({
txRequest: { ...wrapTxRequest, nonce },
account,
inputCurrencyAmount,
swapTxId: txId,
})
wrapTxHash = wrapResponse?.transactionResponse.hash
}
......
......@@ -3,12 +3,13 @@ import { RestLink } from 'apollo-link-rest'
import { config } from 'uniswap/src/config'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { createNewInMemoryCache } from 'uniswap/src/data/cache'
import { REQUEST_SOURCE } from 'uniswap/src/data/constants'
import { REQUEST_SOURCE, getVersionHeader } from 'uniswap/src/data/constants'
export const TRADING_API_HEADERS = {
'Content-Type': 'application/json',
'X-API-KEY': config.tradingApiKey,
'x-request-source': REQUEST_SOURCE,
'x-app-version': getVersionHeader(),
Origin: uniswapUrls.requestOriginUrl,
} as const
......
......@@ -12,6 +12,13 @@ import { sumGasFees } from 'wallet/src/features/transactions/swap/utils'
export type SwapTxAndGasInfo = ClassicSwapTxAndGasInfo | UniswapXSwapTxAndGasInfo
export type UniswapXGasBreakdown = {
classicGasUseEstimateUSD?: number
approvalCost?: string
wrapCost?: string
inputTokenSymbol?: string
}
export type ClassicSwapTxAndGasInfo = {
routing: Routing.CLASSIC
trade?: ClassicTrade
......@@ -28,6 +35,7 @@ export type UniswapXSwapTxAndGasInfo = {
approveTxRequest: ValidatedTransactionRequest | undefined
orderParams?: OrderRequest
gasFee: GasFeeResult
gasFeeBreakdown: UniswapXGasBreakdown
approvalError: boolean
}
......@@ -91,6 +99,13 @@ export function useSwapTxAndGasInfo({ derivedSwapInfo }: { derivedSwapInfo: Deri
if (trade?.routing === Routing.DUTCH_V2) {
const signature = swapTxInfo.permitSignature
const orderParams = signature ? { signature, quote: trade.quote.quote, routing: Routing.DUTCH_V2 } : undefined
const gasFeeBreakdown: UniswapXGasBreakdown = {
// TODO(API-324): next version of trading api schema will break the following line; update the type's field to be a string instead
classicGasUseEstimateUSD: trade.quote.quote.classicGasUseEstimateUSD,
approvalCost: tokenApprovalInfo?.gasFee,
wrapCost: swapTxInfo.gasFeeResult.value,
inputTokenSymbol: trade.inputAmount.currency.wrapped.symbol,
}
return {
routing: Routing.DUTCH_V2,
......@@ -99,6 +114,7 @@ export function useSwapTxAndGasInfo({ derivedSwapInfo }: { derivedSwapInfo: Deri
approveTxRequest,
orderParams,
gasFee,
gasFeeBreakdown,
approvalError,
}
} else {
......
......@@ -4,7 +4,7 @@ import { useMemo } from 'react'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { useRestQuery } from 'uniswap/src/data/rest'
import { isMainnetChainId } from 'uniswap/src/features/chains/utils'
import { DynamicConfigs, PollingIntervalsConfigKey } from 'uniswap/src/features/gating/configs'
import { DynamicConfigs, SwapConfigKey } from 'uniswap/src/features/gating/configs'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useDynamicConfigValue, useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { CurrencyField } from 'uniswap/src/features/transactions/transactionState/types'
......@@ -253,14 +253,14 @@ const FALLBACK_L2_BLOCK_TIME_MS = 3000
function usePollingIntervalByChain(chainId?: WalletChainId): number {
const averageL1BlockTimeMs = useDynamicConfigValue(
DynamicConfigs.PollingIntervals,
PollingIntervalsConfigKey.AverageL1BlockTimeMs,
DynamicConfigs.Swap,
SwapConfigKey.AverageL1BlockTimeMs,
FALLBACK_L1_BLOCK_TIME_MS,
)
const averageL2BlockTimeMs = useDynamicConfigValue(
DynamicConfigs.PollingIntervals,
PollingIntervalsConfigKey.AverageL2BlockTimeMs,
DynamicConfigs.Swap,
SwapConfigKey.AverageL2BlockTimeMs,
FALLBACK_L2_BLOCK_TIME_MS,
)
......
......@@ -3,7 +3,7 @@ import { providers } from 'ethers'
import { useEffect, useMemo, useRef } from 'react'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { useRestQuery } from 'uniswap/src/data/rest'
import { DynamicConfigs, PollingIntervalsConfigKey } from 'uniswap/src/features/gating/configs'
import { DynamicConfigs, SwapConfigKey } from 'uniswap/src/features/gating/configs'
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { CurrencyField } from 'uniswap/src/features/transactions/transactionState/types'
......@@ -116,8 +116,8 @@ export function useTransactionRequestInfo({
// We will remove this cast in follow up change to dynamic config typing
const tradingApiSwapRequestMs = useDynamicConfigValue(
DynamicConfigs.PollingIntervals,
PollingIntervalsConfigKey.TradingApiSwapRequestMs,
DynamicConfigs.Swap,
SwapConfigKey.TradingApiSwapRequestMs,
FALLBACK_SWAP_REQUEST_POLL_INTERVAL_MS,
)
......
import { useMemo } from 'react'
import { isMainnetChainId, toSupportedChainId } from 'uniswap/src/features/chains/utils'
import { DynamicConfigs, SlippageConfigKey } from 'uniswap/src/features/gating/configs'
import { DynamicConfigs, SwapConfigKey } from 'uniswap/src/features/gating/configs'
import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
import { MAX_AUTO_SLIPPAGE_TOLERANCE, MIN_AUTO_SLIPPAGE_TOLERANCE } from 'wallet/src/constants/transactions'
import {
......@@ -63,7 +63,11 @@ export function useSetTradeSlippage(
function useCalculateAutoSlippage(trade: Maybe<Trade>): number {
const outputAmountUSD = useUSDCValue(trade?.outputAmount)?.toExact()
const minAutoSlippageToleranceL2 = useSlippageValueFromDynamicConfig(SlippageConfigKey.MinAutoSlippageToleranceL2)
const minAutoSlippageToleranceL2 = useDynamicConfigValue(
DynamicConfigs.Swap,
SwapConfigKey.MinAutoSlippageToleranceL2,
0,
)
return useMemo<number>(() => {
const quote = getClassicQuoteFromResponse(trade?.quote)
......@@ -110,10 +114,3 @@ function calculateAutoSlippage({
return Number(suggestedSlippageTolerance.toFixed(2))
}
function useSlippageValueFromDynamicConfig(configName: SlippageConfigKey): number {
const slippageValue = useDynamicConfigValue(DynamicConfigs.Slippage, configName, '')
// Format as % number
return parseInt(slippageValue, 10)
}
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { providers } from 'ethers'
import { useCallback } from 'react'
import { CurrencyField } from 'uniswap/src/features/transactions/transactionState/types'
import { WalletChainId } from 'uniswap/src/types/chains'
import { useAsyncData } from 'utilities/src/react/hooks'
import { Trade } from 'wallet/src/features/transactions/swap/trade/types'
import { isUniswapX } from 'wallet/src/features/transactions/swap/trade/utils'
import { DerivedSwapInfo } from 'wallet/src/features/transactions/swap/types'
import { getWethContract } from 'wallet/src/features/transactions/swap/wrapSaga'
......@@ -16,18 +14,11 @@ export function useWrapTransactionRequest(derivedSwapInfo: DerivedSwapInfo): pro
const address = useActiveAccountAddressWithThrow()
const { chainId, wrapType, currencyAmounts, trade } = derivedSwapInfo
const provider = useProvider(chainId)
const isUniswapXWrap = Boolean(trade.trade && isUniswapX(trade.trade) && trade.trade.needsWrap)
const transactionFetcher = useCallback(
() =>
getWrapTransactionRequest(
provider,
trade.trade,
chainId,
address,
wrapType,
currencyAmounts[CurrencyField.INPUT],
),
[address, chainId, wrapType, currencyAmounts, provider, trade.trade],
() => getWrapTransactionRequest(provider, isUniswapXWrap, chainId, address, wrapType, currencyAmounts.input),
[provider, isUniswapXWrap, chainId, address, wrapType, currencyAmounts.input],
)
return useAsyncData(transactionFetcher).data
......@@ -35,13 +26,12 @@ export function useWrapTransactionRequest(derivedSwapInfo: DerivedSwapInfo): pro
const getWrapTransactionRequest = async (
provider: providers.Provider | null,
trade: Trade | null,
isUniswapXWrap: boolean,
chainId: WalletChainId,
address: Address,
wrapType: WrapType,
currencyAmountIn: Maybe<CurrencyAmount<Currency>>,
): Promise<providers.TransactionRequest | undefined> => {
const isUniswapXWrap = trade && isUniswapX(trade) && trade.needsWrap
if (!currencyAmountIn || !provider || (wrapType === WrapType.NotApplicable && !isUniswapXWrap)) {
return
}
......
......@@ -13,6 +13,7 @@ const wrapTxInfo: WrapTransactionInfo = {
type: TransactionType.Wrap,
unwrapped: false,
currencyAmountRaw: '200000',
swapTxId: undefined,
}
const unwrapTxInfo: WrapTransactionInfo = {
......
......@@ -13,6 +13,8 @@ import { createMonitoredSaga } from 'wallet/src/utils/saga'
export type WrapParams = {
txId?: string
// The id that will be used for the swap submitted after the wrap, if applicable.
swapTxId?: string
txRequest: providers.TransactionRequest
account: Account
inputCurrencyAmount: CurrencyAmount<Currency>
......@@ -24,7 +26,7 @@ export async function getWethContract(chainId: WalletChainId, provider: provider
export function* wrap(params: WrapParams) {
try {
const { account, inputCurrencyAmount, txRequest, txId } = params
const { account, inputCurrencyAmount, txRequest, txId, swapTxId } = params
let typeInfo: TransactionTypeInfo
if (inputCurrencyAmount.currency.isNative) {
......@@ -32,12 +34,14 @@ export function* wrap(params: WrapParams) {
type: TransactionType.Wrap,
unwrapped: false,
currencyAmountRaw: inputCurrencyAmount.quotient.toString(),
swapTxId,
}
} else {
typeInfo = {
type: TransactionType.Wrap,
unwrapped: true,
currencyAmountRaw: inputCurrencyAmount.quotient.toString(),
swapTxId,
}
}
......
import { providers } from 'ethers'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { iconSizes } from 'ui/src/theme'
import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants'
import { CurrencyField } from 'uniswap/src/features/transactions/transactionState/types'
import { NumberType } from 'utilities/src/format/types'
import { AccountDetails } from 'wallet/src/components/accounts/AccountDetails'
import { WarningModal } from 'wallet/src/components/modals/WarningModal/WarningModal'
import { useAppFiatCurrencyInfo } from 'wallet/src/features/fiatCurrency/hooks'
import { GasFeeResult } from 'wallet/src/features/gas/types'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { TransactionDetails } from 'wallet/src/features/transactions/TransactionDetails/TransactionDetails'
import { AddressFooter } from 'wallet/src/features/transactions/TransactionRequest/AddressFooter'
import { TransactionReview } from 'wallet/src/features/transactions/TransactionReview/TransactionReview'
import { WarningSeverity } from 'wallet/src/features/transactions/WarningModal/types'
import { ParsedWarnings } from 'wallet/src/features/transactions/hooks/useParsedTransactionWarnings'
......@@ -119,7 +118,7 @@ export function TransferReview({
recipient={recipient}
transactionDetails={
<TransactionDetails
AccountDetails={<AccountDetails address={userAddress} iconSize={iconSizes.icon20} />}
AccountDetails={<AddressFooter activeAccountAddress={userAddress} />}
chainId={chainId}
gasFee={gasFee}
showWarning={Boolean(transferWarning)}
......
......@@ -77,9 +77,6 @@ export interface UniswapXOrderDetails extends BaseTransactionDetails {
// Used to track status of the order before it is submitted
queueStatus?: QueuedOrderStatus
// The txHash of the wrap transaction submitted before the order
wrapTxHash?: string
}
export interface ClassicTransactionDetails extends BaseTransactionDetails {
......@@ -207,6 +204,8 @@ export interface ApproveTransactionInfo extends BaseTransactionInfo {
spender: string
approvalAmount?: string
dappInfo?: DappInfoTransactionDetails
// The id of the swap TransactionDetails object submitted after this approval on the current client, if applicable.
swapTxId?: string
}
export interface BaseSwapTransactionInfo extends BaseTransactionInfo {
......@@ -244,6 +243,9 @@ export interface WrapTransactionInfo extends BaseTransactionInfo {
type: TransactionType.Wrap
unwrapped: boolean
currencyAmountRaw: string
// The id of the swap TransactionDetails object submitted after this wrap on the current client, if applicable.
// Currently, this will only be set for wraps that are part of a UniswapX native-input swap.
swapTxId?: string
}
export interface SendTokenTransactionInfo extends BaseTransactionInfo {
......
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