diff --git a/src/__swaps__/screens/Swap/components/AnimatedBlurView.tsx b/src/__swaps__/screens/Swap/components/AnimatedBlurView.tsx deleted file mode 100644 index 11f4a44f76a..00000000000 --- a/src/__swaps__/screens/Swap/components/AnimatedBlurView.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { BlurView } from '@react-native-community/blur'; -import Animated from 'react-native-reanimated'; - -export const AnimatedBlurView = Animated.createAnimatedComponent(BlurView); diff --git a/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx b/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx index 5f510346f37..b4ff054d2e1 100644 --- a/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx +++ b/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx @@ -5,10 +5,10 @@ import { SharedValue, useAnimatedStyle, useDerivedValue, withRepeat, withSequenc import { opacity } from '@/__swaps__/utils/swaps'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useDelayedValue } from '@/hooks/reanimated/useDelayedValue'; -import { pulsingConfig, sliderConfig } from '../constants'; -import { GasSettings } from '../hooks/useCustomGas'; -import { useSwapEstimatedGasFee } from '../hooks/useEstimatedGasFee'; -import { useSwapContext } from '../providers/swap-provider'; +import { pulsingConfig, sliderConfig } from '@/__swaps__/screens/Swap/constants'; +import { GasSettings } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; +import { useSwapEstimatedGasFee } from '@/__swaps__/screens/Swap/hooks/useEstimatedGasFee'; +import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; type EstimatedSwapGasFeeProps = { gasSettings?: GasSettings } & Partial< Pick diff --git a/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx b/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx index 17a14866819..556ac371cee 100644 --- a/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx +++ b/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx @@ -12,7 +12,7 @@ import { AddressZero } from '@ethersproject/constants'; import { ETH_ADDRESS } from '@/references'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { GestureHandlerButton } from './GestureHandlerButton'; -import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/helpers/utilities'; import { useAccountSettings } from '@/hooks'; export const ExchangeRateBubble = () => { diff --git a/src/__swaps__/screens/Swap/components/FlipButton.tsx b/src/__swaps__/screens/Swap/components/FlipButton.tsx index 2e400d2717d..3baa0974095 100644 --- a/src/__swaps__/screens/Swap/components/FlipButton.tsx +++ b/src/__swaps__/screens/Swap/components/FlipButton.tsx @@ -8,7 +8,7 @@ import { Bleed, Box, IconContainer, Text, globalColors, useColorMode } from '@/d import { SEPARATOR_COLOR } from '@/__swaps__/screens/Swap/constants'; import { getColorValueForThemeWorklet, opacity } from '@/__swaps__/utils/swaps'; import { IS_ANDROID, IS_IOS } from '@/env'; -import { AnimatedBlurView } from '@/__swaps__/screens/Swap/components/AnimatedBlurView'; +import { AnimatedBlurView } from '@/components/AnimatedComponents/AnimatedBlurView'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { SwapAssetType } from '@/__swaps__/types/swap'; @@ -90,7 +90,7 @@ export const FlipButton = () => { return { shadowColor: isDarkMode ? globalColors.grey100 - : getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.mixedShadowColor, false, true), + : getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.mixedShadowColor, false), }; }); @@ -162,7 +162,7 @@ const SpinnerComponent = () => { const animatedColor = useDerivedValue(() => { return withTiming( - getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true), + getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode), TIMING_CONFIGS.slowFadeConfig ); }); diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index 5e8a2761156..59cee69c319 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -1,7 +1,7 @@ import { GasSpeed } from '@/__swaps__/types/gas'; -import { weiToGwei } from '@/__swaps__/utils/ethereum'; +import { weiToGwei } from '@/parsers'; import { getCachedCurrentBaseFee, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; -import { add, formatNumber } from '@/__swaps__/utils/numbers'; +import { add, formatNumber } from '@/helpers/utilities'; import { getColorValueForThemeWorklet } from '@/__swaps__/utils/swaps'; import { ButtonPressAnimation } from '@/components/animations'; import { ContextMenu } from '@/components/context-menu'; @@ -211,7 +211,7 @@ export function ReviewGasButton() { const animatedBorderColor = useAnimatedStyle(() => { return { - borderColor: getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true), + borderColor: getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode), }; }); diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index 25da25d052c..ed1e8dc646d 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -6,7 +6,7 @@ import { MIN_FLASHBOTS_PRIORITY_FEE, THICK_BORDER_WIDTH } from '@/__swaps__/scre import { NavigationSteps, useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { ChainId } from '@/chains/types'; import { GasSpeed } from '@/__swaps__/types/gas'; -import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; +import { gweiToWei, weiToGwei } from '@/parsers'; import { getCachedCurrentBaseFee, getSelectedSpeedSuggestion, @@ -16,13 +16,12 @@ import { useMeteorologySuggestion, useMeteorologySuggestions, } from '@/__swaps__/utils/meteorology'; -import { add, formatNumber, greaterThan, multiply, subtract } from '@/__swaps__/utils/numbers'; +import { add, greaterThan, multiply, subtract, lessThan, formatNumber } from '@/helpers/utilities'; import { opacity } from '@/__swaps__/utils/swaps'; import { ButtonPressAnimation } from '@/components/animations'; import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; import { Bleed, Box, Inline, Separator, Stack, Text, globalColors, useColorMode, useForegroundColor } from '@/design-system'; import { IS_ANDROID } from '@/env'; -import { lessThan } from '@/helpers/utilities'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index eca49b3f12f..504fd18b672 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -2,12 +2,7 @@ import { AnimatedChainImage } from '@/__swaps__/screens/Swap/components/Animated import { ReviewGasButton } from '@/__swaps__/screens/Swap/components/GasButton'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { useEstimatedTime } from '@/__swaps__/utils/meteorology'; -import { - convertRawAmountToBalance, - convertRawAmountToBalanceWorklet, - handleSignificantDecimals, - multiply, -} from '@/__swaps__/utils/numbers'; +import { convertRawAmountToBalance, convertRawAmountToBalanceWorklet, handleSignificantDecimals, multiply } from '@/helpers/utilities'; import { opacity } from '@/__swaps__/utils/swaps'; import { ButtonPressAnimation } from '@/components/animations'; import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; @@ -29,7 +24,6 @@ import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { swapsStore, useSwapsStore } from '@/state/swaps/swapsStore'; -import { getNativeAssetForNetwork } from '@/utils/ethereumUtils'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; import React, { useCallback } from 'react'; import { StyleSheet, View } from 'react-native'; @@ -48,7 +42,7 @@ import { NavigationSteps, useSwapContext } from '../providers/swap-provider'; import { AnimatedSwitch } from './AnimatedSwitch'; import { EstimatedSwapGasFee, EstimatedSwapGasFeeSlot } from './EstimatedSwapGasFee'; import { UnmountOnAnimatedReaction } from './UnmountOnAnimatedReaction'; -import { chainsLabel } from '@/chains'; +import { chainsLabel, chainsNativeAsset } from '@/chains'; import { ChainId } from '@/chains/types'; const UNKNOWN_LABEL = i18n.t(i18n.l.swap.unknown); @@ -354,8 +348,7 @@ export function ReviewPanel() { }); const openGasExplainer = useCallback(async () => { - const nativeAsset = await getNativeAssetForNetwork({ chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet }); - + const nativeAsset = chainsNativeAsset[swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet]; navigate(Routes.EXPLAIN_SHEET, { chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet, type: 'gas', diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index 4d8f2dab126..13e1d722d95 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -61,7 +61,7 @@ function SwapButton({ }); const secondaryTextStyles = useAnimatedStyle(() => { - const secondaryColor = getColorValueForThemeWorklet(asset.value?.textColor, isDarkMode, true); + const secondaryColor = getColorValueForThemeWorklet(asset.value?.textColor, isDarkMode); let opacity = isDarkMode ? 0.76 : 0.8; if (secondaryColor === globalColors.grey100) { @@ -75,9 +75,7 @@ function SwapButton({ const buttonWrapperStyles = useAnimatedStyle(() => { return { - backgroundColor: outline - ? 'transparent' - : getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode, true) || fallbackColor, + backgroundColor: outline ? 'transparent' : getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode) || fallbackColor, borderColor: outline ? separatorSecondary : undefined, borderRadius: borderRadius ?? 24, height: small ? 36 : 48, @@ -169,7 +167,7 @@ const HoldProgress = ({ holdProgress }: { holdProgress: SharedValue }) = const { internalSelectedOutputAsset } = useSwapContext(); const [brightenedColor, setBrightenedColor] = useState( - transformColor(getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true), false) + transformColor(getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode), false) ); const holdProgressStyle = useAnimatedStyle(() => { @@ -192,7 +190,7 @@ const HoldProgress = ({ holdProgress }: { holdProgress: SharedValue }) = () => internalSelectedOutputAsset.value?.highContrastColor, (current, previous) => { if (current && current !== previous) { - runOnJS(transformColor)(getColorValueForThemeWorklet(current, isDarkMode, true)); + runOnJS(transformColor)(getColorValueForThemeWorklet(current, isDarkMode)); } }, [] diff --git a/src/__swaps__/screens/Swap/components/SwapBackground.tsx b/src/__swaps__/screens/Swap/components/SwapBackground.tsx index 594b33f61bc..365707c24e6 100644 --- a/src/__swaps__/screens/Swap/components/SwapBackground.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBackground.tsx @@ -18,17 +18,17 @@ export const SwapBackground = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const animatedTopColor = useDerivedValue(() => { - if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode); return withTiming( - getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), + getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode), TIMING_CONFIGS.slowFadeConfig ); }); const animatedBottomColor = useDerivedValue(() => { - if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode); return withTiming( - getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), + getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode), TIMING_CONFIGS.slowFadeConfig ); }); diff --git a/src/__swaps__/screens/Swap/components/SwapCoinIconTextFallback.tsx b/src/__swaps__/screens/Swap/components/SwapCoinIconTextFallback.tsx index 306e17711da..99c36792fcc 100644 --- a/src/__swaps__/screens/Swap/components/SwapCoinIconTextFallback.tsx +++ b/src/__swaps__/screens/Swap/components/SwapCoinIconTextFallback.tsx @@ -50,7 +50,7 @@ export const SwapCoinIconTextFallback = ({ asset, height, width, style }: SwapCo const backgroundColor = useAnimatedStyle(() => { return { - backgroundColor: getColorValueForThemeWorklet(asset.value?.color, isDarkMode, true), + backgroundColor: getColorValueForThemeWorklet(asset.value?.color, isDarkMode), }; }); @@ -61,7 +61,7 @@ export const SwapCoinIconTextFallback = ({ asset, height, width, style }: SwapCo const animatedFontSize = useAnimatedStyle(() => { return { fontSize: buildFallbackFontSize(formattedSymbol.value, width), - color: getColorValueForThemeWorklet(asset.value?.textColor, isDarkMode, true), + color: getColorValueForThemeWorklet(asset.value?.textColor, isDarkMode), }; }); diff --git a/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx b/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx index 99c57675935..c7c33a83733 100644 --- a/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx +++ b/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { StyleSheet } from 'react-native'; import Animated, { Easing, SharedValue, useAnimatedStyle, withRepeat, withSequence, withTiming } from 'react-native-reanimated'; import { SLIDER_COLLAPSED_HEIGHT, SLIDER_HEIGHT, caretConfig } from '@/__swaps__/screens/Swap/constants'; -import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { equalWorklet } from '@/safe-math/SafeMath'; import { NavigationSteps } from '@/__swaps__/screens/Swap/hooks/useSwapNavigation'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { inputKeys } from '@/__swaps__/types/swap'; @@ -65,7 +65,7 @@ export function SwapInputValuesCaret({ inputCaretType, disabled }: { inputCaretT const selectedAsset = inputCaretType === 'inputAmount' || inputCaretType === 'inputNativeValue' ? internalSelectedInputAsset : internalSelectedOutputAsset; return { - backgroundColor: getColorValueForThemeWorklet(selectedAsset.value?.highContrastColor, isDarkMode, true), + backgroundColor: getColorValueForThemeWorklet(selectedAsset.value?.highContrastColor, isDarkMode), }; }); diff --git a/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx b/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx index 7432d641750..a3e9338e550 100644 --- a/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx +++ b/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx @@ -6,7 +6,7 @@ import Animated, { runOnJS, useAnimatedStyle, useDerivedValue } from 'react-nati import { SwapInputValuesCaret } from '@/__swaps__/screens/Swap/components/SwapInputValuesCaret'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { equalWorklet } from '@/safe-math/SafeMath'; export function SwapNativeInput({ nativeInputType, diff --git a/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx b/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx index b9b9a5286a4..7f3269be7ed 100644 --- a/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx +++ b/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx @@ -13,7 +13,7 @@ import Animated, { } from 'react-native-reanimated'; import { supportedNativeCurrencies } from '@/references'; import { Bleed, Box, Columns, HitSlop, Separator, Text, useColorMode, useForegroundColor } from '@/design-system'; -import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { equalWorklet } from '@/safe-math/SafeMath'; import { stripNonDecimalNumbers } from '@/__swaps__/utils/swaps'; import { CUSTOM_KEYBOARD_HEIGHT, diff --git a/src/__swaps__/screens/Swap/components/SwapSlider.tsx b/src/__swaps__/screens/Swap/components/SwapSlider.tsx index 42581fd1087..a845fb8b25c 100644 --- a/src/__swaps__/screens/Swap/components/SwapSlider.tsx +++ b/src/__swaps__/screens/Swap/components/SwapSlider.tsx @@ -22,7 +22,7 @@ import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animatio import { AnimatedText, Bleed, Box, Column, Columns, Inline, globalColors, useColorMode, useForegroundColor } from '@/design-system'; import { IS_IOS } from '@/env'; import { triggerHapticFeedback } from '@/screens/points/constants'; -import { greaterThanWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { greaterThanWorklet } from '@/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_COLLAPSED_HEIGHT, @@ -90,18 +90,18 @@ export const SwapSlider = ({ const colors = useDerivedValue(() => ({ inactiveColorLeft: opacityWorklet( dualColor - ? getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true) - : getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), + ? getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode) + : getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode), 0.9 ), activeColorLeft: dualColor - ? getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true) - : getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), + ? getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode) + : getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode), inactiveColorRight: dualColor - ? opacityWorklet(getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), 0.9) + ? opacityWorklet(getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode), 0.9) : separatorSecondary, activeColorRight: dualColor - ? getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true) + ? getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode) : fillSecondary, })); diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index 8abd43133c1..42bbfa26ff3 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -7,7 +7,8 @@ import { ChainId } from '@/chains/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { parseSearchAsset } from '@/__swaps__/utils/assets'; -import { getChainColorWorklet, getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { getChainColorWorklet } from '@/__swaps__/utils/swaps'; +import { getUniqueId } from '@/utils/ethereumUtils'; import { analyticsV2 } from '@/analytics'; import { AnimatedTextIcon } from '@/components/AnimatedComponents/AnimatedTextIcon'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; @@ -99,10 +100,7 @@ export const TokenToBuyList = () => { const handleSelectToken = useCallback( (token: SearchAsset) => { runOnUI(() => { - if ( - internalSelectedInputAsset.value && - getStandardizedUniqueIdWorklet({ address: token.address, chainId: token.chainId }) !== internalSelectedOutputAsset.value?.uniqueId - ) { + if (internalSelectedInputAsset.value && getUniqueId(token.address, token.chainId) !== internalSelectedOutputAsset.value?.uniqueId) { isQuoteStale.value = 1; isFetching.value = true; } diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx index e23d5208845..653897d423b 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx @@ -4,7 +4,7 @@ import { ListEmpty } from '@/__swaps__/screens/Swap/components/TokenList/ListEmp import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; import { SwapAssetType } from '@/__swaps__/types/swap'; -import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { getUniqueId } from '@/utils/ethereumUtils'; import { analyticsV2 } from '@/analytics'; import { useDelayedMount } from '@/hooks/useDelayedMount'; import * as i18n from '@/languages'; @@ -45,10 +45,7 @@ const TokenToSellListComponent = () => { if (!token) return; runOnUI(() => { - if ( - internalSelectedOutputAsset.value && - getStandardizedUniqueIdWorklet({ address: token.address, chainId: token.chainId }) !== internalSelectedInputAsset.value?.uniqueId - ) { + if (internalSelectedOutputAsset.value && getUniqueId(token.address, token.chainId) !== internalSelectedInputAsset.value?.uniqueId) { isQuoteStale.value = 1; isFetching.value = true; } diff --git a/src/__swaps__/screens/Swap/constants.ts b/src/__swaps__/screens/Swap/constants.ts index 2ccb5d877c5..22ff49ca2d6 100644 --- a/src/__swaps__/screens/Swap/constants.ts +++ b/src/__swaps__/screens/Swap/constants.ts @@ -1,4 +1,4 @@ -import { gweiToWei } from '@/__swaps__/utils/ethereum'; +import { gweiToWei } from '@/parsers'; import { getDefaultKeyboardHeight } from '@/redux/keyboardHeight'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; import { Easing, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated'; diff --git a/src/__swaps__/screens/Swap/dummyValues.ts b/src/__swaps__/screens/Swap/dummyValues.ts deleted file mode 100644 index ab4bb9be3c8..00000000000 --- a/src/__swaps__/screens/Swap/dummyValues.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Network } from '@/chains/types'; - -// /---- 🗑️ TODO: Delete these dummy values 🗑️ ----/ // -// -// 1. Colors -export const DAI_COLOR = '#F0B340'; -export const SOCKS_COLOR = '#E15EE5'; -export const USDC_COLOR = '#2775CA'; - -// 2. Addresses -export const ETH_ADDRESS = 'eth'; -export const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; -export const SOCKS_ADDRESS = '0x23B608675a2B2fB1890d3ABBd85c5775c51691d5'; -export const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - -// 3. Input Config -export const INPUT_SYMBOL = 'ETH'; -export const INPUT_ADDRESS = ETH_ADDRESS; -// export const INPUT_COLOR = ETH_COLOR_DARK; -export const INPUT_NETWORK = Network.base; -export const INPUT_ASSET_BALANCE = 12.4; -// export const INPUT_ASSET_BALANCE = 12.42512485; -export const INPUT_ASSET_USD_PRICE = 2632.4; -export const IS_INPUT_STABLECOIN = false; - -// 4. Output Config -// export const OUTPUT_SYMBOL = 'USDC'; -// export const OUTPUT_ADDRESS = USDC_ADDRESS; -// export const OUTPUT_COLOR = USDC_COLOR; -// export const OUTPUT_NETWORK = Network.base; -// export const OUTPUT_ASSET_USD_PRICE = 0.999; -// export const IS_OUTPUT_STABLECOIN = true; -export const OUTPUT_SYMBOL = 'SOCKS'; -export const OUTPUT_ADDRESS = SOCKS_ADDRESS; -export const OUTPUT_COLOR = SOCKS_COLOR; -export const OUTPUT_NETWORK = Network.base; -export const OUTPUT_ASSET_USD_PRICE = 41963.7; -export const IS_OUTPUT_STABLECOIN = false; - -// 5. Swap Config -export const SWAP_FEE = 0.0085; -// -// /---- END dummy values ----/ // diff --git a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts index 244dd9f993e..d7ff8bf21ec 100644 --- a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts @@ -161,7 +161,7 @@ export function useAnimatedSwapStyles({ }); const outputAssetColor = useDerivedValue(() => { - return getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true); + return getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode); }); const swapActionWrapperStyle = useAnimatedStyle(() => { @@ -202,13 +202,13 @@ export function useAnimatedSwapStyles({ const assetToSellIconStyle = useAnimatedStyle(() => { return { - backgroundColor: getColorValueForThemeWorklet(internalSelectedInputAsset.value?.color, isDarkMode, true), + backgroundColor: getColorValueForThemeWorklet(internalSelectedInputAsset.value?.color, isDarkMode), }; }); const assetToBuyIconStyle = useAnimatedStyle(() => { return { - backgroundColor: getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.color, isDarkMode, true), + backgroundColor: getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.color, isDarkMode), }; }); @@ -221,7 +221,7 @@ export function useAnimatedSwapStyles({ const searchInputAssetButtonStyle = useAnimatedStyle(() => { return { - color: getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), + color: getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode), }; }); @@ -233,7 +233,7 @@ export function useAnimatedSwapStyles({ const color = isPasteMode.value ? foregroundColors.blue : internalSelectedOutputAsset.value?.highContrastColor; return { - color: getColorValueForThemeWorklet(color, isDarkMode, true), + color: getColorValueForThemeWorklet(color, isDarkMode), }; }); @@ -257,11 +257,11 @@ export function useAnimatedSwapStyles({ const searchInputAssetButtonWrapperStyle = useAnimatedStyle(() => { return { backgroundColor: opacityWorklet( - getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), + getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode), isDarkMode ? 0.1 : 0.08 ), borderColor: opacityWorklet( - getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), + getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode), isDarkMode ? 0.06 : 0.01 ), }; @@ -276,9 +276,9 @@ export function useAnimatedSwapStyles({ return { backgroundColor: isPasteMode.value ? 'transparent' - : opacityWorklet(getColorValueForThemeWorklet(color, isDarkMode, true), isDarkMode ? 0.1 : 0.08), + : opacityWorklet(getColorValueForThemeWorklet(color, isDarkMode), isDarkMode ? 0.1 : 0.08), borderColor: opacityWorklet( - getColorValueForThemeWorklet(color, isDarkMode, true), + getColorValueForThemeWorklet(color, isDarkMode), isDarkMode ? darkModeBorderOpacity : lightModeBorderOpacity ), }; diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToBuy.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToBuy.ts deleted file mode 100644 index b2fd3713a51..00000000000 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToBuy.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useMemo } from 'react'; -import { useSearchCurrencyLists } from '@/__swaps__/screens/Swap/hooks/useSearchCurrencyLists'; - -export const useAssetsToBuySections = () => { - const { results: searchAssetsToBuySections } = useSearchCurrencyLists(); - - return useMemo(() => searchAssetsToBuySections, [searchAssetsToBuySections]); -}; diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts deleted file mode 100644 index e78faa661c4..00000000000 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo } from 'react'; - -import { - selectUserAssetsList, - selectUserAssetsListByChainId, - selectorFilterByUserChains, -} from '@/__swaps__/screens/Swap/resources/_selectors/assets'; -import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; -import { ParsedAssetsDictByChain, ParsedSearchAsset, UserAssetFilter } from '@/__swaps__/types/assets'; -import { useAccountSettings, useDebounce } from '@/hooks'; -import { useUserAssetsStore } from '@/state/assets/userAssets'; - -const sortBy = (by: UserAssetFilter) => { - switch (by) { - case 'all': - return selectUserAssetsList; - default: - return (data: ParsedAssetsDictByChain) => selectUserAssetsListByChainId(data, by); - } -}; - -export const useAssetsToSell = () => { - const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); - - const { filter, searchQuery } = useUserAssetsStore(state => ({ - filter: state.filter, - searchQuery: state.inputSearchQuery, - })); - - const debouncedAssetToSellFilter = useDebounce(searchQuery, 200); - - const { data: userAssets = [] } = useUserAssets( - { - address: currentAddress, - currency: currentCurrency, - }, - { - select: data => - selectorFilterByUserChains({ - data, - selector: sortBy(filter), - }), - } - ); - - const filteredAssetsToSell = useMemo(() => { - return debouncedAssetToSellFilter - ? userAssets.filter(({ name, symbol, address }) => - [name, symbol, address].reduce( - (res, param) => res || param.toLowerCase().startsWith(debouncedAssetToSellFilter.toLowerCase()), - false - ) - ) - : userAssets; - }, [debouncedAssetToSellFilter, userAssets]) as ParsedSearchAsset[]; - - return filteredAssetsToSell; -}; diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index 9d47932ba9b..26d89a65f44 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -1,6 +1,6 @@ import { ChainId } from '@/chains/types'; -import { weiToGwei } from '@/__swaps__/utils/ethereum'; -import { add, convertAmountToNativeDisplayWorklet, formatNumber, multiply } from '@/__swaps__/utils/numbers'; +import { weiToGwei } from '@/parsers'; +import { convertAmountToNativeDisplayWorklet, formatNumber, multiply } from '@/helpers/utilities'; import { useNativeAsset } from '@/utils/ethereumUtils'; import { useMemo } from 'react'; import { formatUnits } from 'viem'; diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts deleted file mode 100644 index 87e94dc22af..00000000000 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useCallback } from 'react'; - -import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/chains/types'; - -import { SharedValue, runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated'; -import { ParsedAddressAsset } from '@/entities'; -import { ethereumUtils } from '@/utils'; -import { swapsStore } from '@/state/swaps/swapsStore'; - -export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue }) => { - const preferredNetwork = swapsStore.getState().preferredNetwork; - const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? preferredNetwork ?? ChainId.mainnet); - const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset({ chainId: chainId.value })); - - const getNativeAssetForNetwork = useCallback( - (chainId: ChainId) => { - const asset = ethereumUtils.getNetworkNativeAsset({ chainId }); - nativeAsset.value = asset; - }, - [nativeAsset] - ); - - useAnimatedReaction( - () => chainId.value, - (currentChainId, previousChainId) => { - if (currentChainId !== previousChainId) { - runOnJS(getNativeAssetForNetwork)(currentChainId); - } - }, - [] - ); - - return { - nativeAsset, - }; -}; diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index bf88f7abf6f..82e78bed66c 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -1,13 +1,11 @@ import { TokenSearchResult, useTokenSearch } from '@/__swaps__/screens/Swap/resources/search/search'; -import { AddressOrEth } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; import { SearchAsset, TokenSearchAssetKey, TokenSearchThreshold } from '@/__swaps__/types/search'; -import { addHexPrefix } from '@/__swaps__/utils/hex'; -import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; -import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { addHexPrefix } from '@/handlers/web3'; +import { isLowerCaseMatch, filterList } from '@/utils'; +import { getUniqueId } from '@/utils/ethereumUtils'; import { useFavorites } from '@/resources/favorites'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { filterList } from '@/utils'; import { isAddress } from '@ethersproject/address'; import { rankings } from 'match-sorter'; import { useCallback, useMemo, useState } from 'react'; @@ -348,10 +346,7 @@ export function useSearchCurrencyLists() { chainId: state.toChainId, favorite: true, mainnetAddress: favToken.networks?.[ChainId.mainnet]?.address || favToken.mainnet_address, - uniqueId: getStandardizedUniqueIdWorklet({ - address: (favToken.networks[state.toChainId]?.address || favToken.address) as AddressOrEth, - chainId: state.toChainId, - }), + uniqueId: getUniqueId(favToken.networks[state.toChainId]?.address || favToken.address, state.toChainId), })) as SearchAsset[]; }, [favorites, state.toChainId]); diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputStyles.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputStyles.ts index 783c2e4fe16..3654817ede5 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputStyles.ts @@ -35,7 +35,7 @@ export const useSwapInputStyles = ({ const bgColor = useDerivedValue(() => { return isDarkMode - ? opacityWorklet(getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode, true), 0.08) + ? opacityWorklet(getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode), 0.08) : opacityWorklet(globalColors.white100, 0.8); }); @@ -46,9 +46,9 @@ export const useSwapInputStyles = ({ const strokeColor = useDerivedValue(() => { return isDarkMode ? opacityWorklet( - getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode, true) === ETH_COLOR_DARK + getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode) === ETH_COLOR_DARK ? ETH_COLOR_DARK_ACCENT - : getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode, true), + : getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode), 0.06 ) : globalColors.white100; @@ -56,7 +56,7 @@ export const useSwapInputStyles = ({ const expandedStrokeColor = useDerivedValue(() => { return isDarkMode - ? opacityWorklet(getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode, true), 0.1) + ? opacityWorklet(getColorValueForThemeWorklet(asset.value?.highContrastColor, isDarkMode), 0.1) : globalColors.white100; }); @@ -78,7 +78,7 @@ export const useSwapInputStyles = ({ otherInputProgress.value === NavigationSteps.SEARCH_FOCUSED ? withTiming(0, TIMING_CONFIGS.fadeConfig) : withTiming(1, TIMING_CONFIGS.fadeConfig), - shadowColor: isDarkMode ? 'transparent' : getColorValueForThemeWorklet(asset.value?.mixedShadowColor, isDarkMode, true), + shadowColor: isDarkMode ? 'transparent' : getColorValueForThemeWorklet(asset.value?.mixedShadowColor, isDarkMode), transform: [ { translateY: withSpring(getContainerStyleTranslateY(progress, bottomInput), SPRING_CONFIGS.keyboardConfig), diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 877ef1eb5b1..4c2a4ac8905 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,11 +1,4 @@ -import { - divWorklet, - equalWorklet, - greaterThanWorklet, - isNumberStringWorklet, - mulWorklet, - toFixedWorklet, -} from '@/__swaps__/safe-math/SafeMath'; +import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet, toFixedWorklet } from '@/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; @@ -16,7 +9,7 @@ import { convertAmountToNativeDisplayWorklet, convertRawAmountToDecimalFormat, handleSignificantDecimalsWorklet, -} from '@/__swaps__/utils/numbers'; +} from '@/helpers/utilities'; import { addCommasToNumber, addSymbolToNativeDisplayWorklet, diff --git a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts index be633447abb..e8ef1b63085 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts @@ -14,7 +14,7 @@ import { ETH_COLOR_DARK, ETH_COLOR_DARK_ACCENT, pulsingConfig } from '@/__swaps_ import { inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { getColorValueForThemeWorklet, opacity } from '@/__swaps__/utils/swaps'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { equalWorklet } from '@/safe-math/SafeMath'; import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs'; export function useSwapTextStyles({ @@ -87,12 +87,12 @@ export function useSwapTextStyles({ }); const inputAssetColor = useDerivedValue(() => { - const color = getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true); + const color = getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode); return color === ETH_COLOR_DARK ? ETH_COLOR_DARK_ACCENT : color; }); const outputAssetColor = useDerivedValue(() => { - const color = getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true); + const color = getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode); return color === ETH_COLOR_DARK ? ETH_COLOR_DARK_ACCENT : color; }); diff --git a/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts b/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts index bc9c7611a53..3af47091b59 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapWarning.ts @@ -7,9 +7,9 @@ import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; import { getCrossChainTimeEstimateWorklet, getQuoteServiceTimeWorklet } from '@/__swaps__/utils/swaps'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; import { highPriceImpactThreshold, severePriceImpactThreshold } from '@/__swaps__/screens/Swap/constants'; -import { divWorklet, greaterThanOrEqualToWorklet, subWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { divWorklet, greaterThanOrEqualToWorklet, subWorklet } from '@/safe-math/SafeMath'; import { inputValuesType } from '@/__swaps__/types/swap'; -import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/helpers/utilities'; export enum SwapWarningType { unknown = 'unknown', diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx index 99e1dfc56b8..65476eb5fd6 100644 --- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx +++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx @@ -11,7 +11,7 @@ import { sumWorklet, toFixedWorklet, toScaledIntegerWorklet, -} from '@/__swaps__/safe-math/SafeMath'; +} from '@/safe-math/SafeMath'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; import { ParsedAddressAsset } from '@/entities'; diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index d0e320c0cfc..6f0cd81ec29 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -14,7 +14,7 @@ import { useSharedValue, } from 'react-native-reanimated'; -import { equalWorklet, lessThanOrEqualToWorklet, sumWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { equalWorklet, lessThanOrEqualToWorklet, sumWorklet } from '@/safe-math/SafeMath'; import { INITIAL_SLIDER_POSITION, SLIDER_COLLAPSED_HEIGHT, SLIDER_HEIGHT, SLIDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { useAnimatedSwapStyles } from '@/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles'; import { useSwapInputsController } from '@/__swaps__/screens/Swap/hooks/useSwapInputsController'; @@ -406,8 +406,6 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const isNativeWrapOrUnwrap = quoteData.swapType === SwapType.wrap || quoteData.swapType === SwapType.unwrap; - // Do not deleeeet the comment below 😤 - // About to get quote const parameters: Omit, 'gasParams' | 'gasFeeParamsBySpeed' | 'selectedGasFee'> = { sellAmount: quoteData.sellAmount?.toString(), buyAmount: quoteData.buyAmount?.toString(), diff --git a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts index 4c3e8d8dbbf..4ca4805c1c7 100644 --- a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts +++ b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts @@ -1,7 +1,7 @@ import { ParsedAssetsDict, ParsedAssetsDictByChain, ParsedUserAsset, UniqueId } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; -import { deriveAddressAndChainWithUniqueId } from '@/__swaps__/utils/address'; -import { add } from '@/__swaps__/utils/numbers'; +import { getAddressAndChainIdFromUniqueId } from '@/utils/ethereumUtils'; +import { add } from '@/helpers/utilities'; // selectors export function selectorFilterByUserChains({ @@ -34,14 +34,6 @@ export function selectUserAssetsDictByChain(assets: ParsedAssetsDictByChain) { return assets; } -export function selectUserAssetsListByChainId(assets: ParsedAssetsDictByChain, chainId: ChainId) { - const assetsForNetwork = assets?.[chainId]; - - return Object.values(assetsForNetwork).sort( - (a: ParsedUserAsset, b: ParsedUserAsset) => parseFloat(b?.native?.balance?.amount) - parseFloat(a?.native?.balance?.amount) - ); -} - export function selectUserAssetAddressMapByChainId(assets: ParsedAssetsDictByChain) { const mapAddresses = (list: ParsedAssetsDict = {}) => Object.values(list).map(i => i.address); return { @@ -59,8 +51,8 @@ export function selectUserAssetAddressMapByChainId(assets: ParsedAssetsDictByCha // selector generators export function selectUserAssetWithUniqueId(uniqueId: UniqueId) { return (assets: ParsedAssetsDictByChain) => { - const { chain } = deriveAddressAndChainWithUniqueId(uniqueId); - return assets?.[chain]?.[uniqueId]; + const { chainId } = getAddressAndChainIdFromUniqueId(uniqueId); + return assets?.[chainId]?.[uniqueId]; }; } diff --git a/src/__swaps__/screens/Swap/resources/assets/assets.ts b/src/__swaps__/screens/Swap/resources/assets/assets.ts deleted file mode 100644 index d9e01be8db7..00000000000 --- a/src/__swaps__/screens/Swap/resources/assets/assets.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import { requestMetadata } from '@/graphql'; -import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; -import { SupportedCurrencyKey } from '@/references'; -import { AddressOrEth, AssetMetadata, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/chains/types'; -import { chunkArray, createAssetQuery, parseAssetMetadata } from '@/__swaps__/utils/assets'; -import { RainbowError, logger } from '@/logger'; -export const ASSETS_TIMEOUT_DURATION = 10000; -const ASSETS_REFETCH_INTERVAL = 60000; - -// /////////////////////////////////////////////// -// Query Types - -export type AssetsQueryArgs = { - assetAddresses: AddressOrEth[]; - chainId: ChainId; - currency: SupportedCurrencyKey; -}; - -// /////////////////////////////////////////////// -// Query Key - -const assetsQueryKey = ({ assetAddresses, chainId, currency }: AssetsQueryArgs) => - createQueryKey('assets', { assetAddresses, chainId, currency }, { persisterVersion: 2 }); - -type AssetsQueryKey = ReturnType; - -// /////////////////////////////////////////////// -// Query Function - -export async function assetsQueryFunction({ - queryKey: [{ assetAddresses, chainId, currency }], -}: QueryFunctionArgs): Promise<{ - [key: UniqueId]: ParsedAsset; -}> { - try { - if (!assetAddresses || !assetAddresses.length) return {}; - const batches = chunkArray([...assetAddresses], 10); // chunking because a full batch would throw 413 - const batchQueries = batches.map(batchedQuery => createAssetQuery(batchedQuery, chainId, currency, true), { - timeout: ASSETS_TIMEOUT_DURATION, - }); - - const batchResults = batchQueries.map(query => requestMetadata(query)) as Promise[]>[]; - const results = (await Promise.all(batchResults)) - .flat() - .map(r => Object.values(r)) - .flat(); - const parsedAssets = parseAssets(results, chainId, currency); - return parsedAssets; - } catch (e) { - logger.error(new RainbowError('[assetsQueryFunction]: Failed to fetch assets'), { - message: (e as Error)?.message, - }); - return {}; - } -} - -type AssetsQueryResult = QueryFunctionResult; - -// /////////////////////////////////////////////// -// Query Fetcher - -export async function fetchAssets( - { assetAddresses, chainId, currency }: AssetsQueryArgs, - config: QueryConfigWithSelect = {} -) { - return await queryClient.fetchQuery(assetsQueryKey({ assetAddresses, chainId, currency }), assetsQueryFunction, config); -} - -function parseAssets(assets: AssetMetadata[], chainId: ChainId, currency: SupportedCurrencyKey) { - return assets.reduce( - (assetsDict, asset) => { - const address = asset.networks?.[chainId]?.address; - if (address) { - const parsedAsset = parseAssetMetadata({ - address, - asset, - chainId, - currency, - }); - assetsDict[parsedAsset?.uniqueId] = parsedAsset; - } - return assetsDict; - }, - {} as Record - ); -} - -// /////////////////////////////////////////////// -// Query Hook - -export function useAssets( - { assetAddresses, chainId, currency }: AssetsQueryArgs, - config: QueryConfigWithSelect = {} -) { - return useQuery(assetsQueryKey({ assetAddresses, chainId, currency }), assetsQueryFunction, { - ...config, - refetchInterval: ASSETS_REFETCH_INTERVAL, - }); -} diff --git a/src/__swaps__/screens/Swap/resources/assets/index.ts b/src/__swaps__/screens/Swap/resources/assets/index.ts index f485a083154..ae84cfa4ecc 100644 --- a/src/__swaps__/screens/Swap/resources/assets/index.ts +++ b/src/__swaps__/screens/Swap/resources/assets/index.ts @@ -1,4 +1,3 @@ -export { useAssets } from './assets'; export { useUserAssets } from './userAssets'; export type { UserAssetsArgs } from './userAssets'; export { useUserAssetsByChain } from './userAssetsByChain'; diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index 8fbe3f35a91..3feb82a433e 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -11,7 +11,7 @@ import { ParsedAssetsDictByChain, ZerionAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { parseUserAsset } from '@/__swaps__/utils/assets'; -import { greaterThan } from '@/__swaps__/utils/numbers'; +import { greaterThan } from '@/helpers/utilities'; import { fetchUserAssetsByChain } from './userAssetsByChain'; import { fetchHardhatBalancesByChainId } from '@/resources/assets/hardhatAssets'; diff --git a/src/__swaps__/utils/__tests__/numbers.test.ts b/src/__swaps__/utils/__tests__/numbers.test.ts index faf3be13c6b..7c4675acc04 100644 --- a/src/__swaps__/utils/__tests__/numbers.test.ts +++ b/src/__swaps__/utils/__tests__/numbers.test.ts @@ -1,4 +1,4 @@ -import { convertAmountToNativeDisplayWorklet } from '../numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/helpers/utilities'; import { supportedCurrencies } from '@/references/supportedCurrencies'; const testCases = [ diff --git a/src/__swaps__/utils/address.ts b/src/__swaps__/utils/address.ts deleted file mode 100644 index 1573c130a76..00000000000 --- a/src/__swaps__/utils/address.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Address } from 'viem'; - -import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/chains/types'; - -export function truncateAddress(address?: AddressOrEth) { - if (!address) return ''; - return `${address?.slice(0, 6)}…${address?.slice(-4)}`; -} - -export function deriveAddressAndChainWithUniqueId(uniqueId: UniqueId) { - const fragments = uniqueId.split('_'); - const address = fragments[0] as Address; - const chain = parseInt(fragments[1], 10) as ChainId; - return { - address, - chain, - }; -} diff --git a/src/__swaps__/utils/assets.ts b/src/__swaps__/utils/assets.ts index 7f5d6f0bb9b..87a135deec8 100644 --- a/src/__swaps__/utils/assets.ts +++ b/src/__swaps__/utils/assets.ts @@ -1,6 +1,4 @@ -import { AddressZero } from '@ethersproject/constants'; - -import { ETH_ADDRESS, SupportedCurrencyKey } from '@/references'; +import { SupportedCurrencyKey } from '@/references'; import { AddressOrEth, AssetApiResponse, @@ -17,41 +15,25 @@ import { ChainId, ChainName } from '@/chains/types'; import * as i18n from '@/languages'; import { SearchAsset } from '@/__swaps__/types/search'; -import { customChainIdsToAssetNames, isNativeAsset } from '@/__swaps__/utils/chains'; +import { isNativeAsset } from '@/handlers/assets'; import { convertAmountAndPriceToNativeDisplay, convertAmountToBalanceDisplay, convertAmountToNativeDisplayWorklet, convertAmountToPercentageDisplay, convertRawAmountToDecimalFormat, -} from '@/__swaps__/utils/numbers'; -import { isLowerCaseMatch, isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; +} from '@/helpers/utilities'; +import { isLowerCaseMatch } from '@/utils'; import { chainsIdByName, chainsName } from '@/chains'; export const isSameAsset = (a1: Pick, a2: Pick) => +a1.chainId === +a2.chainId && isLowerCaseMatch(a1.address, a2.address); -export const isSameAssetWorklet = (a1: Pick, a2: Pick) => { - 'worklet'; - return +a1.chainId === +a2.chainId && isLowerCaseMatchWorklet(a1.address, a2.address); -}; - const get24HrChange = (priceData?: ZerionAssetPrice) => { const twentyFourHrChange = priceData?.relative_change_24h; return twentyFourHrChange ? convertAmountToPercentageDisplay(twentyFourHrChange) : ''; }; -export const getCustomChainIconUrl = (chainId: ChainId, address: AddressOrEth) => { - if (!chainId || !customChainIdsToAssetNames[chainId]) return ''; - const baseUrl = 'https://raw.githubusercontent.com/rainbow-me/assets/master/blockchains/'; - - if (address === AddressZero || address === ETH_ADDRESS) { - return `${baseUrl}${customChainIdsToAssetNames[chainId]}/info/logo.png`; - } else { - return `${baseUrl}${customChainIdsToAssetNames[chainId]}/assets/${address}/logo.png`; - } -}; - export const getNativeAssetPrice = ({ priceData, currency }: { priceData?: ZerionAssetPrice; currency: SupportedCurrencyKey }) => { const priceUnit = priceData?.value; return { @@ -121,7 +103,7 @@ export function parseAsset({ asset, currency }: { asset: ZerionAsset | AssetApiR symbol: asset.symbol, type: asset.type, decimals: asset.decimals, - icon_url: asset.icon_url || getCustomChainIconUrl(chainId, address), + icon_url: asset.icon_url, colors: asset.colors, standard, ...('networks' in asset && { networks: asset.networks }), @@ -304,42 +286,3 @@ export const parseSearchAsset = ({ colors: userAsset?.colors || assetWithPrice?.colors || searchAsset?.colors, type: userAsset?.type || assetWithPrice?.type || searchAsset?.type, }); - -const assetQueryFragment = ( - address: AddressOrEth, - chainId: ChainId, - currency: SupportedCurrencyKey, - index: number, - withPrice?: boolean -) => { - const priceQuery = withPrice ? 'price { value relativeChange24h }' : ''; - return `Q${index}: token(address: "${address}", chainID: ${chainId}, currency: "${currency}") { - colors { - primary - fallback - shadow - } - decimals - iconUrl - name - networks - symbol - ${priceQuery} - }`; -}; - -export const chunkArray = (arr: TItem[], chunkSize: number) => { - const result = []; - - for (let i = 0; i < arr.length; i += chunkSize) { - result.push(arr.slice(i, i + chunkSize)); - } - - return result; -}; - -export const createAssetQuery = (addresses: AddressOrEth[], chainId: ChainId, currency: SupportedCurrencyKey, withPrice?: boolean) => { - return `{ - ${addresses.map((a, i) => assetQueryFragment(a, chainId, currency, i, withPrice)).join(',')} - }`; -}; diff --git a/src/__swaps__/utils/chains.ts b/src/__swaps__/utils/chains.ts deleted file mode 100644 index 63b1748d97a..00000000000 --- a/src/__swaps__/utils/chains.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { celo, fantom, harmonyOne, moonbeam } from 'viem/chains'; -import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId } from '@/chains/types'; -import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; -import { chainsNativeAsset } from '@/chains'; - -// @ts-expect-error Property '[ChainId.hardhat]' is missing -export const customChainIdsToAssetNames: Record = { - 42170: 'arbitrumnova', - 1313161554: 'aurora', - 43114: 'avalanchex', - 81457: 'blast', - 168587773: 'blastsepolia', - 288: 'boba', - 42220: 'celo', - 61: 'classic', - 25: 'cronos', - 2000: 'dogechain', - 250: 'fantom', - 314: 'filecoin', - 1666600000: 'harmony', - 13371: 'immutablezkevm', - 2222: 'kavaevm', - 8217: 'klaytn', - 59144: 'linea', - 957: 'lyra', - 169: 'manta', - 5000: 'mantle', - 1088: 'metis', - 34443: 'mode', - 1284: 'moonbeam', - 7700: 'nativecanto', - 204: 'opbnb', - 11297108109: 'palm', - 424: 'pgn', - 1101: 'polygonzkevm', - 369: 'pulsechain', - 1380012617: 'rari', - 1918988905: 'raritestnet', - 17001: 'redstoneholesky', - 534352: 'scroll', - 100: 'xdai', - 324: 'zksync', -}; - -export function isNativeAsset(address: AddressOrEth, chainId: ChainId) { - return isLowerCaseMatch(chainsNativeAsset[chainId].address, address); -} - -export const chainIdToUse = (connectedToHardhat: boolean, connectedToHardhatOp: boolean, activeSessionChainId: number) => { - if (connectedToHardhat) { - return ChainId.hardhat; - } - if (connectedToHardhatOp) { - return ChainId.hardhatOptimism; - } - return activeSessionChainId; -}; - -export const deriveChainIdByHostname = (hostname: string) => { - switch (hostname) { - case 'etherscan.io': - return ChainId.mainnet; - case 'arbiscan.io': - return ChainId.arbitrum; - case 'explorer-mumbai.maticvigil.com': - case 'explorer-mumbai.matic.today': - case 'mumbai.polygonscan.com': - return ChainId.polygonMumbai; - case 'polygonscan.com': - return ChainId.polygon; - case 'optimistic.etherscan.io': - return ChainId.optimism; - case 'bscscan.com': - return ChainId.bsc; - case 'ftmscan.com': - return fantom.id; - case 'explorer.celo.org': - return celo.id; - case 'explorer.harmony.one': - return harmonyOne.id; - case 'explorer.avax.network': - case 'subnets.avax.network': - case 'snowtrace.io': - return ChainId.avalanche; - case 'subnets-test.avax.network': - case 'testnet.snowtrace.io': - return ChainId.avalancheFuji; - case 'moonscan.io': - return moonbeam.id; - case 'explorer.holesky.redstone.xyz': - return 17001; - case 'blastscan.io': - return ChainId.blast; - case 'testnet.blastscan.io': - return 168587773; - default: - return ChainId.mainnet; - } -}; diff --git a/src/__swaps__/utils/decimalFormatter.ts b/src/__swaps__/utils/decimalFormatter.ts index fc0f9a943ec..437a707d150 100644 --- a/src/__swaps__/utils/decimalFormatter.ts +++ b/src/__swaps__/utils/decimalFormatter.ts @@ -7,7 +7,7 @@ import { toFixedWorklet, orderOfMagnitudeWorklet, significantDecimalsWorklet, -} from '../safe-math/SafeMath'; +} from '@/safe-math/SafeMath'; const MAXIMUM_SIGNIFICANT_DECIMALS = 6; const STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS = 2; diff --git a/src/__swaps__/utils/ethereum.ts b/src/__swaps__/utils/ethereum.ts deleted file mode 100644 index d929152b3f2..00000000000 --- a/src/__swaps__/utils/ethereum.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { isAddress } from '@ethersproject/address'; -import { Mnemonic, isValidMnemonic } from '@ethersproject/hdnode'; -import { parseEther } from '@ethersproject/units'; -import { Address } from 'viem'; -import { EthereumPrivateKey } from '@/model/wallet'; -import { ethUnits } from '@/references'; - -import { addHexPrefix, isHexStringIgnorePrefix } from '@/__swaps__/utils/hex'; -import { divide, multiply } from '@/__swaps__/utils/numbers'; - -export type EthereumWalletSeed = EthereumPrivateKey | Mnemonic['phrase']; -export enum EthereumWalletType { - mnemonic = 'mnemonic', - privateKey = 'privateKey', - readOnly = 'readOnly', - seed = 'seed', - ledgerPublicKey = 'ledgerPublicKey', - trezorPublicKey = 'trezorPublicKey', -} - -const validTLDs = ['eth', 'xyz', 'luxe', 'kred', 'reverse', 'addr', 'test']; -export const isENSAddressFormat = (name: string) => { - if (!name) return false; - const tld = name.split('.').at(-1); - if (!tld || tld === name) return false; - return validTLDs.includes(tld.toLowerCase()); -}; - -/** - * @desc Checks if a string is a valid private key. - * @param value The string. - * @return Whether or not the string is a valid private key string. - */ -export const isValidPrivateKey = (value: string): boolean => { - return isHexStringIgnorePrefix(value) && addHexPrefix(value).length === 66; -}; - -export const identifyWalletType = (walletSeed: EthereumWalletSeed): EthereumWalletType => { - if (isValidPrivateKey(walletSeed)) { - return EthereumWalletType.privateKey; - } - // 12 or 24 words seed phrase - if (isValidMnemonic(walletSeed)) { - return EthereumWalletType.mnemonic; - } - // Public address (0x) - if (isAddress(walletSeed)) { - return EthereumWalletType.readOnly; - } - // seed - return EthereumWalletType.seed; -}; - -/** - * @desc Checks if a an address has previous transactions - * @param {String} address - * @return {Promise} - */ -export const hasPreviousTransactions = async (address: Address): Promise => { - try { - const url = `https://aha.rainbow.me/?address=${address}`; - const response = await fetch(url); - if (!response.ok) { - return false; - } - - const parsedResponse = (await response.json()) as { - data: { addresses: Record }; - }; - - return parsedResponse?.data?.addresses[address.toLowerCase()] === true; - } catch (e) { - return false; - } -}; - -export const gweiToWei = (gweiAmount: string) => { - const weiAmount = multiply(gweiAmount, ethUnits.gwei); - return weiAmount; -}; - -export const weiToGwei = (weiAmount: string) => { - const gweiAmount = divide(weiAmount, ethUnits.gwei); - return gweiAmount; -}; - -export const toWei = (ether: string): string => { - const result = parseEther(ether); - return result.toString(); -}; - -// This function removes all the keys from the message that are not present in the types -// preventing a know phising attack where the signature process could allow malicious DApps -// to trick users into signing an EIP-712 object different from the one presented -// in the signature approval preview. Consequently, users were at risk of unknowingly -// transferring control of their ERC-20 tokens, NFTs, etc to adversaries by signing -// hidden Permit messages. - -// For more info read https://www.coinspect.com/wallet-EIP-712-injection-vulnerability/ - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const sanitizeTypedData = (data: any) => { - if (data.types[data.primaryType].length > 0) { - // Extract all the valid permit types for the primary type - const permitPrimaryTypes: string[] = data.types[data.primaryType].map((type: { name: string; type: string }) => type.name); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const sanitizedMessage: any = {}; - // Extract all the message keys that matches the valid permit types - Object.keys(data.message).forEach(key => { - if (permitPrimaryTypes.includes(key)) { - sanitizedMessage[key] = data.message[key]; - } - }); - - const sanitizedData = { - ...data, - message: sanitizedMessage, - }; - - return sanitizedData; - } - return data; -}; diff --git a/src/__swaps__/utils/flipAssets.ts b/src/__swaps__/utils/flipAssets.ts index 3947b7b546d..683ee713139 100644 --- a/src/__swaps__/utils/flipAssets.ts +++ b/src/__swaps__/utils/flipAssets.ts @@ -3,7 +3,7 @@ import { inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap import { valueBasedDecimalFormatter } from '@/__swaps__/utils/decimalFormatter'; import { niceIncrementFormatter } from '@/__swaps__/utils/swaps'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { divWorklet, equalWorklet, greaterThanWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { divWorklet, equalWorklet, greaterThanWorklet, mulWorklet } from '@/safe-math/SafeMath'; export function getInputValuesForSliderPositionWorklet({ selectedInputAsset, diff --git a/src/__swaps__/utils/gasUtils.ts b/src/__swaps__/utils/gasUtils.ts index 2052ca02b18..c3fc6c3d4dd 100644 --- a/src/__swaps__/utils/gasUtils.ts +++ b/src/__swaps__/utils/gasUtils.ts @@ -14,9 +14,10 @@ import { ParsedAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; import { BlocksToConfirmation, GasFeeLegacyParams, GasFeeParam, GasFeeParams, GasSpeed } from '@/__swaps__/types/gas'; -import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; -import { addHexPrefix, convertStringToHex, toHex } from '@/__swaps__/utils/hex'; +import { gweiToWei, weiToGwei } from '@/parsers'; import { + convertStringToHex, + lessThan, add, addBuffer, convertAmountAndPriceToNativeDisplay, @@ -24,11 +25,11 @@ import { divide, fraction, greaterThan, - lessThan, multiply, -} from '@/__swaps__/utils/numbers'; -import { getMinimalTimeUnitStringForMs } from '@/__swaps__/utils/time'; +} from '@/helpers/utilities'; +import { addHexPrefix, toHex } from '@/handlers/web3'; import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/entities/gas'; +import { getMinimalTimeUnitStringForMs } from '@/helpers/time'; export const FLASHBOTS_MIN_TIP = 6; diff --git a/src/__swaps__/utils/hex.ts b/src/__swaps__/utils/hex.ts deleted file mode 100644 index 8b00190bdd7..00000000000 --- a/src/__swaps__/utils/hex.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { isHexString } from '@ethersproject/bytes'; -import BigNumber from 'bignumber.js'; -import { startsWith } from 'lodash'; - -export type BigNumberish = number | string | BigNumber; - -/** - * @desc Checks if a hex string, ignoring prefixes and suffixes. - * @param value The string. - * @return Whether or not the string is a hex string. - */ -export const isHexStringIgnorePrefix = (value: string): boolean => { - if (!value) return false; - const trimmedValue = value.trim(); - const updatedValue = addHexPrefix(trimmedValue); - return isHexString(updatedValue); -}; - -/** - * @desc Adds an "0x" prefix to a string if one is not present. - * @param value The starting string. - * @return The prefixed string. - */ -export const addHexPrefix = (value: string): string => (startsWith(value, '0x') ? value : `0x${value}`); - -export const addHexPrefixWorklet = (value: string): string => { - 'worklet'; - return startsWith(value, '0x') ? value : `0x${value}`; -}; - -export const convertStringToHex = (stringToConvert: string): string => new BigNumber(stringToConvert).toString(16); - -export const toHex = (stringToConvert: string): string => addHexPrefix(convertStringToHex(stringToConvert)); - -export const toHexNoLeadingZeros = (value: string): string => toHex(value).replace(/^0x0*/, '0x'); diff --git a/src/__swaps__/utils/meteorology.ts b/src/__swaps__/utils/meteorology.ts index 0af6fbdb855..64c03986f14 100644 --- a/src/__swaps__/utils/meteorology.ts +++ b/src/__swaps__/utils/meteorology.ts @@ -11,8 +11,8 @@ import { MIN_FLASHBOTS_PRIORITY_FEE } from '../screens/Swap/constants'; import { GasSettings } from '../screens/Swap/hooks/useCustomGas'; import { getSelectedGasSpeed, useGasSettings } from '../screens/Swap/hooks/useSelectedGas'; import { GasSpeed } from '../types/gas'; -import { getMinimalTimeUnitStringForMs } from './time'; import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/entities/gas'; +import { getMinimalTimeUnitStringForMs } from '@/helpers/time'; // Query Types diff --git a/src/__swaps__/utils/numbers.ts b/src/__swaps__/utils/numbers.ts deleted file mode 100644 index c6c3e11a115..00000000000 --- a/src/__swaps__/utils/numbers.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { BigNumber as EthersBigNumber } from '@ethersproject/bignumber'; -import BigNumber from 'bignumber.js'; -import currency from 'currency.js'; -import { isNil } from 'lodash'; - -import { supportedNativeCurrencies } from '@/references'; -import { BigNumberish } from '@/__swaps__/utils/hex'; -import { divWorklet, lessThanWorklet, orderOfMagnitudeWorklet, powWorklet } from '../safe-math/SafeMath'; - -type nativeCurrencyType = typeof supportedNativeCurrencies; - -export const toBigNumber = (v?: string | number | BigNumber) => (v ? EthersBigNumber.from(v) : undefined); - -export const abs = (value: BigNumberish): string => new BigNumber(value).abs().toFixed(); - -export const isPositive = (value: BigNumberish): boolean => new BigNumber(value).isPositive(); - -export const subtract = (numberOne: BigNumberish, numberTwo: BigNumberish): string => - new BigNumber(numberOne).minus(new BigNumber(numberTwo)).toFixed(); - -export const convertAmountToRawAmount = (value: BigNumberish, decimals: number | string): string => - new BigNumber(value).times(new BigNumber(10).pow(decimals)).toFixed(); - -export const isZero = (value: BigNumberish): boolean => new BigNumber(value).isZero(); - -export const toFixedDecimals = (value: BigNumberish, decimals: number): string => new BigNumber(value).toFixed(decimals); - -export const convertNumberToString = (value: BigNumberish): string => new BigNumber(value).toFixed(); - -export const greaterThan = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => new BigNumber(numberOne).gt(numberTwo); - -export const greaterThanOrEqualTo = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => new BigNumber(numberOne).gte(numberTwo); - -export const isEqual = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => new BigNumber(numberOne).eq(numberTwo); - -export const formatFixedDecimals = (value: BigNumberish, decimals: number): string => { - const _value = convertNumberToString(value); - const _decimals = convertStringToNumber(decimals); - return new BigNumber(new BigNumber(_value).toFixed(_decimals)).toFixed(); -}; - -export const mod = (numberOne: BigNumberish, numberTwo: BigNumberish): string => - new BigNumber(numberOne).mod(new BigNumber(numberTwo)).toFixed(); - -/** - * @desc real floor divides two numbers - * @param {Number} numberOne - * @param {Number} numberTwo - * @return {String} - */ -export const floorDivide = (numberOne: BigNumberish, numberTwo: BigNumberish): string => - new BigNumber(numberOne).dividedToIntegerBy(new BigNumber(numberTwo)).toFixed(); - -/** - * @desc count value's number of decimals places - * @param {String} value - * @return {String} - */ -export const countDecimalPlaces = (value: BigNumberish): number => new BigNumber(value).dp(); - -/** - * @desc update the amount to display precision - * equivalent to ~0.01 of the native price - * or use most significant decimal - * if the updated precision amounts to zero - * @param {String} amount - * @param {String} nativePrice - * @param {Boolean} use rounding up mode - * @return {String} updated amount - */ -export const updatePrecisionToDisplay = (amount: BigNumberish, nativePrice?: BigNumberish, roundUp = false): string => { - if (!amount) return '0'; - const roundingMode = roundUp ? BigNumber.ROUND_UP : BigNumber.ROUND_DOWN; - if (!nativePrice) return new BigNumber(amount).decimalPlaces(6, roundingMode).toFixed(); - const bnAmount = new BigNumber(amount); - const significantDigitsOfNativePriceInteger = new BigNumber(nativePrice).decimalPlaces(0, BigNumber.ROUND_DOWN).sd(true); - const truncatedPrecision = new BigNumber(significantDigitsOfNativePriceInteger).plus(2, 10).toNumber(); - const truncatedAmount = bnAmount.decimalPlaces(truncatedPrecision, BigNumber.ROUND_DOWN); - return truncatedAmount.isZero() - ? new BigNumber(bnAmount.toPrecision(1, roundingMode)).toFixed() - : bnAmount.decimalPlaces(truncatedPrecision, roundingMode).toFixed(); -}; - -/** - * @desc format inputOne value to signficant decimals given inputTwo - * @param {String} inputOne - * @param {String} inputTwo - * @return {String} - */ -// TODO revisit logic, at least rename so it is not native amount dp -export const formatInputDecimals = (inputOne: BigNumberish, inputTwo: BigNumberish): string => { - const _nativeAmountDecimalPlaces = countDecimalPlaces(inputTwo); - const decimals = _nativeAmountDecimalPlaces > 8 ? _nativeAmountDecimalPlaces : 8; - const result = new BigNumber(formatFixedDecimals(inputOne, decimals)).toFormat().replace(/,/g, ''); - return result; -}; - -export const add = (numberOne: BigNumberish, numberTwo: BigNumberish): string => new BigNumber(numberOne).plus(numberTwo).toFixed(); - -export const minus = (numberOne: BigNumberish, numberTwo: BigNumberish): string => new BigNumber(numberOne).minus(numberTwo).toFixed(); - -export const addDisplay = (numberOne: string, numberTwo: string): string => { - const unit = numberOne.replace(/[\d.-]/g, ''); - const leftAlignedUnit = numberOne.indexOf(unit) === 0; - return currency(0, { symbol: unit, pattern: leftAlignedUnit ? '!#' : '#!' }) - .add(numberOne) - .add(numberTwo) - .format(); -}; - -export const multiply = (numberOne: BigNumberish, numberTwo: BigNumberish): string => new BigNumber(numberOne).times(numberTwo).toFixed(); - -export const addBuffer = (numberOne: BigNumberish, buffer: BigNumberish = '1.2'): string => - new BigNumber(numberOne).times(buffer).toFixed(0); - -export const divide = (numberOne: BigNumberish, numberTwo: BigNumberish): string => { - if (!(numberOne || numberTwo)) return '0'; - return new BigNumber(numberOne).dividedBy(numberTwo).toFixed(); -}; - -export const fraction = (target: BigNumberish, numerator: BigNumberish, denominator: BigNumberish): string => { - if (!target || !numerator || !denominator) return '0'; - return new BigNumber(target).times(numerator).dividedBy(denominator).toFixed(0); -}; - -/** - * @desc convert to asset amount units from native price value units - * @param {String} value - * @param {Object} asset - * @param {Number} priceUnit - * @return {String} - */ -export const convertAmountFromNativeValue = (value: BigNumberish, priceUnit: BigNumberish, decimals = 18): string => { - if (isNil(priceUnit) || isZero(priceUnit)) return '0'; - return new BigNumber(new BigNumber(value).dividedBy(priceUnit).toFixed(decimals, BigNumber.ROUND_DOWN)).toFixed(); -}; - -export const convertStringToNumber = (value: BigNumberish) => new BigNumber(value).toNumber(); - -export const lessThan = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => new BigNumber(numberOne).lt(numberTwo); - -export const lessOrEqualThan = (numberOne: BigNumberish, numberTwo: BigNumberish): boolean => - new BigNumber(numberOne).lt(numberTwo) || new BigNumber(numberOne).eq(numberTwo); - -export const handleSignificantDecimalsWithThreshold = (value: BigNumberish, decimals: number, threshold = '0.0001') => { - const result = toFixedDecimals(value, decimals); - return lessThan(result, threshold) ? `< ${threshold}` : result; -}; - -export const handleSignificantDecimalsWorklet = (value: number | string, decimals: number, buffer = 3): string => { - 'worklet'; - let dec; - - if (lessThanWorklet(value, 1)) { - const orderOfMagnitude = orderOfMagnitudeWorklet(value); - const sigDigitsWithBuffer = -orderOfMagnitude - 1 + buffer; - dec = Math.min(sigDigitsWithBuffer, 8); - } else { - dec = Math.min(decimals, buffer); - } - return Number(value).toLocaleString('en-US', { - useGrouping: true, - minimumFractionDigits: 2, - maximumFractionDigits: dec, - }); -}; - -export const handleSignificantDecimals = (value: BigNumberish, decimals: number, buffer = 3, skipDecimals = false): string => { - let dec; - if (lessThan(new BigNumber(value).abs(), 1)) { - dec = new BigNumber(value).toFixed()?.slice?.(2).search(/[^0]/g) + buffer; - dec = Math.min(dec, 8); - } else { - dec = Math.min(decimals, buffer); - } - const result = new BigNumber(new BigNumber(value).toFixed(dec)).toFixed(); - const resultBN = new BigNumber(result); - return resultBN.dp() <= 2 ? resultBN.toFormat(skipDecimals ? 0 : 2) : resultBN.toFormat(); -}; - -export const handleSignificantDecimalsAsNumber = (value: BigNumberish, decimals: number): string => { - return new BigNumber(new BigNumber(multiply(value, new BigNumber(10).pow(decimals))).toFixed(0)) - .dividedBy(new BigNumber(10).pow(decimals)) - .toFixed(); -}; - -/** - * @desc convert from asset BigNumber amount to native price BigNumber amount - */ -export const convertAmountToNativeAmount = (amount: BigNumberish, priceUnit: BigNumberish): string => multiply(amount, priceUnit); - -/** - * @desc convert from amount to display formatted string - */ -export const convertAmountAndPriceToNativeDisplay = ( - amount: BigNumberish, - priceUnit: BigNumberish, - nativeCurrency: keyof nativeCurrencyType, - useThreshold = false -): { amount: string; display: string } => { - const nativeBalanceRaw = convertAmountToNativeAmount(amount, priceUnit); - const nativeDisplay = convertAmountToNativeDisplayWorklet(nativeBalanceRaw, nativeCurrency, useThreshold); - return { - amount: nativeBalanceRaw, - display: nativeDisplay, - }; -}; - -/** - * @desc convert from raw amount to display formatted string - */ -export const convertRawAmountToNativeDisplay = ( - rawAmount: BigNumberish, - assetDecimals: number, - priceUnit: BigNumberish, - nativeCurrency: keyof nativeCurrencyType -) => { - const assetBalance = convertRawAmountToDecimalFormat(rawAmount, assetDecimals); - const ret = convertAmountAndPriceToNativeDisplay(assetBalance, priceUnit, nativeCurrency); - return ret; -}; - -/** - * @desc convert from raw amount to decimal format - */ -export const convertRawAmountToDecimalFormatWorklet = (value: number | string, decimals = 18): string => { - 'worklet'; - return divWorklet(value, powWorklet(10, decimals)); -}; - -/** - * @desc convert from amount value to display formatted string - */ -export const convertAmountToBalanceDisplayWorklet = ( - value: number | string, - asset: { decimals: number; symbol?: string }, - buffer?: number -) => { - 'worklet'; - const decimals = asset?.decimals ?? 18; - const display = handleSignificantDecimalsWorklet(value, decimals, buffer); - return `${display} ${asset?.symbol || ''}`; -}; - -/** - * @desc convert from raw amount to balance object - */ -export const convertRawAmountToBalanceWorklet = (value: number | string, asset: { decimals: number; symbol?: string }, buffer?: number) => { - 'worklet'; - const decimals = asset?.decimals ?? 18; - - const assetBalance = convertRawAmountToDecimalFormatWorklet(value, decimals); - - return { - amount: assetBalance, - display: convertAmountToBalanceDisplayWorklet(assetBalance, asset, buffer), - }; -}; - -/** - * @desc convert from raw amount to balance object - */ -export const convertRawAmountToBalance = (value: BigNumberish, asset: { decimals: number; symbol?: string }, buffer?: number) => { - const decimals = asset?.decimals ?? 18; - const assetBalance = convertRawAmountToDecimalFormat(value, decimals); - - return { - amount: assetBalance, - display: convertAmountToBalanceDisplay(assetBalance, asset, buffer), - }; -}; - -/** - * @desc convert from amount value to display formatted string - */ -export const convertAmountToBalanceDisplay = (value: BigNumberish, asset: { decimals: number; symbol?: string }, buffer?: number) => { - const decimals = asset?.decimals ?? 18; - const display = handleSignificantDecimals(value, decimals, buffer); - return `${display} ${asset?.symbol || ''}`; -}; - -/** - * @desc convert from amount to display formatted string - */ -export const convertAmountToPercentageDisplay = (value: BigNumberish, buffer?: number, skipDecimals?: boolean, decimals = 2): string => { - const display = handleSignificantDecimals(value, decimals, buffer, skipDecimals); - return `${display}%`; -}; - -/** - * @desc convert from amount to display formatted string - * with a threshold percent - */ -export const convertAmountToPercentageDisplayWithThreshold = (value: BigNumberish, decimals = 2, threshold = '0.0001'): string => { - if (lessThan(value, threshold)) { - return '< 0.01%'; - } else { - const display = new BigNumber(value).times(100).toFixed(decimals); - return `${display}%`; - } -}; - -/** - * @desc convert from bips amount to percentage format - */ -export const convertBipsToPercentage = (value: BigNumberish, decimals = 2): string => { - if (value === null) return '0'; - return new BigNumber(value || 0).shiftedBy(-2).toFixed(decimals); -}; - -/** - * @desc convert from amount value to display formatted string - */ -export const convertAmountToNativeDisplayWorklet = ( - value: number | string, - nativeCurrency: keyof nativeCurrencyType, - useThreshold = false, - ignoreAlignment = false -) => { - 'worklet'; - - const nativeSelected = supportedNativeCurrencies?.[nativeCurrency]; - const { alignment, decimals: rawDecimals, symbol } = nativeSelected; - const decimals = Math.min(rawDecimals, 6); - - const valueNumber = Number(value); - const threshold = decimals < 4 ? 0.01 : 0.0001; - let thresholdReached = false; - - if (useThreshold && valueNumber < threshold) { - thresholdReached = true; - } - - const nativeValue = thresholdReached - ? threshold - : valueNumber.toLocaleString('en-US', { - useGrouping: true, - minimumFractionDigits: nativeCurrency === 'ETH' ? undefined : decimals, - maximumFractionDigits: decimals, - }); - - const nativeDisplay = `${thresholdReached ? '<' : ''}${alignment === 'left' || ignoreAlignment ? symbol : ''}${nativeValue}${!ignoreAlignment && alignment === 'right' ? symbol : ''}`; - - return nativeDisplay; -}; - -/** - * @desc convert from raw amount to decimal format - */ -export const convertRawAmountToDecimalFormat = (value: BigNumberish, decimals = 18): string => - new BigNumber(value).dividedBy(new BigNumber(10).pow(decimals)).toFixed(); - -/** - * @desc convert from decimal format to raw amount - */ -export const convertDecimalFormatToRawAmount = (value: string, decimals = 18): string => - new BigNumber(value).multipliedBy(new BigNumber(10).pow(decimals)).toFixed(0); - -export const fromWei = (number: BigNumberish): string => convertRawAmountToDecimalFormat(number, 18); - -const decimalSeparator = '.'; -const lessThanPrefix = '<'; - -export const formatNumber = (value: string, options?: { decimals?: number }) => { - if (!+value) return `0${decimalSeparator}0`; - if (+value < 0.0001) return `${lessThanPrefix}0${decimalSeparator}0001`; - - const [whole, fraction = ''] = value.split(decimalSeparator); - const decimals = options?.decimals; - const paddedFraction = `${fraction.padEnd(decimals || 4, '0')}`; - - if (decimals) { - if (decimals === 0) return whole; - return `${whole}${decimalSeparator}${paddedFraction.slice(0, decimals)}`; - } - - if (+whole > 0) return `${whole}${decimalSeparator}${paddedFraction.slice(0, 2)}`; - return `0${decimalSeparator}${paddedFraction.slice(0, 4)}`; -}; diff --git a/src/__swaps__/utils/strings.ts b/src/__swaps__/utils/strings.ts deleted file mode 100644 index dd7c7d26615..00000000000 --- a/src/__swaps__/utils/strings.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const isLowerCaseMatch = (a?: string, b?: string) => a?.toLowerCase() === b?.toLowerCase(); - -export const isLowerCaseMatchWorklet = (a?: string, b?: string) => { - 'worklet'; - return a?.toLowerCase() === b?.toLowerCase(); -}; - -export const capitalize = (s = '') => s.charAt(0).toUpperCase() + s.slice(1); - -export const truncateString = (txt = '', maxLength = 22) => { - return `${txt?.slice(0, maxLength)}${txt.length > maxLength ? '…' : ''}`; -}; diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index d61abbfd6db..514c9d63240 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -33,12 +33,19 @@ import { powWorklet, roundWorklet, toFixedWorklet, -} from '../safe-math/SafeMath'; -import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '../types/assets'; +} from '@/safe-math/SafeMath'; +import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '../types/assets'; import { inputKeys } from '../types/swap'; import { valueBasedDecimalFormatter } from './decimalFormatter'; -import { convertAmountToRawAmount } from './numbers'; +import { convertAmountToRawAmount } from '@/helpers/utilities'; import { chainsName } from '@/chains'; +import { getUniqueId } from '@/utils/ethereumUtils'; + +// DO NOT REMOVE THESE COMMENTED ENV VARS +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { IS_APK_BUILD } from 'react-native-dotenv'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import isTestFlight from '@/helpers/isTestFlight'; // /---- 🎨 Color functions 🎨 ----/ // // @@ -51,14 +58,14 @@ export type ResponseByTheme = { dark: T; }; -export const getColorValueForTheme = (values: ResponseByTheme | undefined, isDarkMode: boolean, useDefaults = false) => { +export const getColorValueForTheme = (values: ResponseByTheme | undefined, isDarkMode: boolean) => { if (!values) { return isDarkMode ? ETH_COLOR_DARK : ETH_COLOR; } return isDarkMode ? values.dark : values.light; }; -export const getColorValueForThemeWorklet = (values: ResponseByTheme | undefined, isDarkMode: boolean, useDefaults = true) => { +export const getColorValueForThemeWorklet = (values: ResponseByTheme | undefined, isDarkMode: boolean) => { 'worklet'; if (!values) { @@ -534,11 +541,6 @@ const ETH_COLORS: Colors = { shadow: undefined, }; -export const getStandardizedUniqueIdWorklet = ({ address, chainId }: { address: AddressOrEth; chainId: ChainId }) => { - 'worklet'; - return `${address}_${chainId}`; -}; - export const parseAssetAndExtend = ({ asset, insertUserAssetBalance, @@ -552,7 +554,7 @@ export const parseAssetAndExtend = ({ colors: (isAssetEth ? ETH_COLORS : asset.colors) as TokenColors, }); - const uniqueId = getStandardizedUniqueIdWorklet({ address: asset.address, chainId: asset.chainId }); + const uniqueId = getUniqueId(asset.address, asset.chainId); const balance = insertUserAssetBalance ? userAssetsStore.getState().getUserAsset(uniqueId)?.balance || asset.balance : asset.balance; return { @@ -598,7 +600,7 @@ export const buildQuoteParams = ({ const isCrosschainSwap = inputAsset.chainId !== outputAsset.chainId; - return { + const quoteParams: QuoteParams = { source: source === 'auto' ? undefined : source, chainId: inputAsset.chainId, fromAddress: currentAddress, @@ -617,4 +619,9 @@ export const buildQuoteParams = ({ toChainId: isCrosschainSwap ? outputAsset.chainId : inputAsset.chainId, currency: store.getState().settings.nativeCurrency, }; + + // Do not delete the comment below 😤 + // @ts-ignore About to get quote + + return quoteParams; }; diff --git a/src/__swaps__/utils/time.ts b/src/__swaps__/utils/time.ts deleted file mode 100644 index 306caa25906..00000000000 --- a/src/__swaps__/utils/time.ts +++ /dev/null @@ -1,49 +0,0 @@ -import parseMilliseconds from 'parse-ms'; - -import * as i18n from '@/languages'; - -export const buildLocalizedTimeUnitString = ({ plural, short, unit }: { plural?: boolean; short: boolean; unit: string }) => { - const length = short ? 'short' : 'long'; - const plurality = plural ? 'plural' : 'singular'; - - return i18n.t(`time.${unit}.${length}.${plurality}`); -}; - -const getHighestResolutionUnit = (timeUnitKey?: string, timeUnitValues?: { [key: string]: number }) => { - const highestResolutionUnit = timeUnitKey || 'seconds'; - return { - unit: highestResolutionUnit, - value: timeUnitValues?.[highestResolutionUnit] || 0, - }; -}; - -/** - * @desc get time string for minimal unit - * @param {String} [value=''] - * @param {Boolean} [short=true] - * @param {Boolean} [plural=false] - * @return {String} - */ -export const getMinimalTimeUnitStringForMs = (value: number, plural?: boolean, short = true): string => { - const ms = Number(value); - - const { days, hours, minutes, seconds } = parseMilliseconds(Number(ms)); - - const times = { days, hours, minutes, seconds }; - const timeUnitKey = Object.entries(times).find(([, value]) => value !== 0)?.[0]; - - const { unit: highestResolutionUnit, value: highestResolutionValue } = getHighestResolutionUnit(timeUnitKey, { - days, - hours, - minutes, - seconds, - }); - - const label = buildLocalizedTimeUnitString({ - plural, - short, - unit: highestResolutionUnit, - }); - - return `${highestResolutionValue} ${label}`; -}; diff --git a/src/__swaps__/utils/userChains.ts b/src/__swaps__/utils/userChains.ts deleted file mode 100644 index d10e850fe4f..00000000000 --- a/src/__swaps__/utils/userChains.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - Chain, - arbitrum, - arbitrumGoerli, - arbitrumSepolia, - avalanche, - avalancheFuji, - base, - baseSepolia, - bsc, - bscTestnet, - holesky, - optimism, - optimismSepolia, - polygon, - polygonMumbai, - zora, - zoraSepolia, - goerli, - mainnet, - sepolia, -} from 'viem/chains'; - -import { ChainId } from '@/chains/types'; -import { chainsLabel } from '@/chains'; - -export const chainIdMap: Record< - ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.base | ChainId.bsc | ChainId.zora | ChainId.avalanche, - ChainId[] -> = { - [ChainId.mainnet]: [mainnet.id, goerli.id, sepolia.id, holesky.id], - [ChainId.optimism]: [optimism.id, optimismSepolia.id], - [ChainId.arbitrum]: [arbitrum.id, arbitrumGoerli.id, arbitrumSepolia.id], - [ChainId.polygon]: [polygon.id, polygonMumbai.id], - [ChainId.base]: [base.id, baseSepolia.id], - [ChainId.bsc]: [bsc.id, bscTestnet.id], - [ChainId.zora]: [zora.id, zoraSepolia.id], - [ChainId.avalanche]: [avalanche.id, avalancheFuji.id], -}; - -export const chainLabelMap: Record< - ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.base | ChainId.bsc | ChainId.zora | ChainId.avalanche, - string[] -> = { - [ChainId.mainnet]: [chainsLabel[goerli.id], chainsLabel[sepolia.id], chainsLabel[holesky.id]], - [ChainId.optimism]: [chainsLabel[optimismSepolia.id]], - [ChainId.arbitrum]: [chainsLabel[arbitrumGoerli.id], chainsLabel[arbitrumSepolia.id]], - [ChainId.polygon]: [chainsLabel[polygonMumbai.id]], - [ChainId.base]: [chainsLabel[baseSepolia.id]], - [ChainId.bsc]: [chainsLabel[bscTestnet.id]], - [ChainId.zora]: [chainsLabel[zoraSepolia.id]], - [ChainId.avalanche]: [chainsLabel[avalancheFuji.id]], -}; - -export const sortNetworks = (order: ChainId[], chains: Chain[]) => { - const allChainsOrder = order?.map(chainId => chainIdMap[chainId] || [chainId])?.flat(); - const ordered = chains.sort((a, b) => { - const aIndex = allChainsOrder.indexOf(a.id); - const bIndex = allChainsOrder.indexOf(b.id); - if (aIndex === -1) return bIndex === -1 ? 0 : 1; - if (bIndex === -1) return -1; - return aIndex - bIndex; - }); - return ordered; -}; - -export const filterUserNetworks = ({ userChains }: { userChains: Record }) => { - const availableChains = Object.keys(userChains) - .filter(chainId => userChains[Number(chainId)] === true) - .map(chainId => Number(chainId)); - - const allAvailableUserChains = availableChains.map(chainId => chainIdMap[chainId]).flat(); - - return allAvailableUserChains; -}; diff --git a/src/components/exchange/CurrencySelectionList.tsx b/src/components/CurrencySelectionList.tsx similarity index 79% rename from src/components/exchange/CurrencySelectionList.tsx rename to src/components/CurrencySelectionList.tsx index ae980536e6c..ecc0d551f83 100644 --- a/src/components/exchange/CurrencySelectionList.tsx +++ b/src/components/CurrencySelectionList.tsx @@ -1,15 +1,15 @@ import React, { forwardRef, ForwardRefRenderFunction } from 'react'; import { SectionList } from 'react-native'; -import { magicMemo } from '../../utils'; -import { EmptyAssetList } from '../asset-list'; -import { Centered } from '../layout'; -import { NoResults } from '../list'; -import { CurrencySelectModalHeaderHeight } from './CurrencySelectModalHeader'; -import ExchangeAssetList from './ExchangeAssetList'; -import { ExchangeSearchHeight } from './ExchangeSearch'; +import { magicMemo } from '@/utils'; +import { EmptyAssetList } from '@/components/asset-list'; +import { Centered } from '@/components/layout'; +import { NoResults } from '@/components/list'; +import ExchangeAssetList, { EnrichedExchangeAsset } from '@/components/ExchangeAssetList'; import { Box } from '@/design-system'; -import { EnrichedExchangeAsset } from '@/screens/CurrencySelectModal'; -import { NoResultsType } from '../list/NoResults'; +import { NoResultsType } from '@/components/list/NoResults'; + +const CurrencySelectModalHeaderHeight = 59; +const SearchHeight = 40; interface CurrencySelectionListProps { keyboardDismissMode?: 'none' | 'interactive' | 'on-drag'; @@ -44,7 +44,7 @@ const CurrencySelectionList: ForwardRefRenderFunction {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} diff --git a/src/components/DappBrowser/BrowserTab.tsx b/src/components/DappBrowser/BrowserTab.tsx index 23e7a37ac8b..02cca1a6425 100644 --- a/src/components/DappBrowser/BrowserTab.tsx +++ b/src/components/DappBrowser/BrowserTab.tsx @@ -42,7 +42,6 @@ import { Homepage } from './Homepage'; import { WebViewBorder } from './WebViewBorder'; import { RAINBOW_HOME, - DEFAULT_TAB_URL, TAB_SCREENSHOT_FILE_FORMAT, TAB_SCREENSHOT_FASTER_IMAGE_CONFIG, USER_AGENT, @@ -196,7 +195,7 @@ const FreezableWebViewComponent = ({ const webViewRef = useRef(null); const isActiveTab = useBrowserStore(state => state.isTabActive(tabId)); - const tabUrl = useBrowserStore(state => state.getTabData?.(tabId)?.url) || DEFAULT_TAB_URL; + const tabUrl = useBrowserStore(state => state.getTabData?.(tabId)?.url) || RAINBOW_HOME; const isOnHomepage = tabUrl === RAINBOW_HOME; const handleOnMessage = useCallback( diff --git a/src/components/DappBrowser/Homepage.tsx b/src/components/DappBrowser/Homepage.tsx index 121b20811ef..09f46f64d26 100644 --- a/src/components/DappBrowser/Homepage.tsx +++ b/src/components/DappBrowser/Homepage.tsx @@ -40,7 +40,7 @@ import { useBrowserContext } from './BrowserContext'; import { getNameFromFormattedUrl } from './utils'; import { useTrendingDApps } from '@/resources/metadata/trendingDapps'; import { DApp } from '@/graphql/__generated__/metadata'; -import { DEFAULT_TAB_URL } from './constants'; +import { RAINBOW_HOME } from './constants'; import { FeaturedResult } from '@/graphql/__generated__/arc'; import { useRemoteConfig } from '@/model/remoteConfig'; import { FEATURED_RESULTS, useExperimentalFlag } from '@/config'; @@ -175,7 +175,7 @@ const Favorites = ({ goToUrl, tabId }: { goToUrl: (url: string) => void; tabId: ({ currentGridSort, isActiveTab }: { currentGridSort: string[] | undefined; isActiveTab: boolean }) => { 'worklet'; const homepageTabsCount = currentlyOpenTabIds.value.filter( - tabId => !animatedTabUrls.value[tabId] || animatedTabUrls.value[tabId] === DEFAULT_TAB_URL + tabId => !animatedTabUrls.value[tabId] || animatedTabUrls.value[tabId] === RAINBOW_HOME ).length; const inactiveAndMounted = !isActiveTab && currentGridSort !== undefined; diff --git a/src/components/DappBrowser/constants.ts b/src/components/DappBrowser/constants.ts index e3bdae2b9bd..c3e29fd02cd 100644 --- a/src/components/DappBrowser/constants.ts +++ b/src/components/DappBrowser/constants.ts @@ -6,8 +6,6 @@ export const HTTP = 'http://'; export const HTTPS = 'https://'; export const RAINBOW_HOME = 'RAINBOW_HOME'; -export const DEFAULT_TAB_URL = RAINBOW_HOME; - export const USER_AGENT = { IOS: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1', ANDROID: 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.103 Mobile Safari/537.36', diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index a7867ee6f68..2358b34601e 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -33,7 +33,6 @@ import { useSyncSharedValue } from '@/hooks/reanimated/useSyncSharedValue'; import { useBrowserStore } from '@/state/browser/browserStore'; import { colors } from '@/styles'; import { deviceUtils, watchingAlert } from '@/utils'; -import ethereumUtils from '@/utils/ethereumUtils'; import { addressHashedEmoji } from '@/utils/profileUtils'; import { getHighContrastTextColorWorklet } from '@/worklets/colors'; import { TOP_INSET } from '../Dimensions'; @@ -45,19 +44,17 @@ import { useDispatch } from 'react-redux'; import store from '@/redux/store'; import { getDappHost } from '@/utils/connectedApps'; import WebView from 'react-native-webview'; -import { Navigation, useNavigation } from '@/navigation'; +import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { address } from '@/utils/abbreviations'; import { fontWithWidthWorklet } from '@/styles/buildTextStyles'; import { useAppSessionsStore } from '@/state/appSessions'; -import { DEFAULT_TAB_URL, RAINBOW_HOME } from '../constants'; +import { RAINBOW_HOME } from '../constants'; import { FavoritedSite, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; import WalletTypes from '@/helpers/walletTypes'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { addressSetSelected, walletsSetSelected } from '@/redux/wallets'; -import { getRemoteConfig } from '@/model/remoteConfig'; -import { SWAPS_V2, useExperimentalFlag } from '@/config'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; import { greaterThan } from '@/helpers/utilities'; @@ -89,7 +86,7 @@ export const ControlPanel = () => { } = useRoute>(); const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); const activeTabUrl = useBrowserStore(state => state.getActiveTabUrl()); - const activeTabHost = getDappHost(activeTabUrl || '') || DEFAULT_TAB_URL; + const activeTabHost = getDappHost(activeTabUrl || '') || RAINBOW_HOME; const updateActiveSessionNetwork = useAppSessionsStore(state => state.updateActiveSessionNetwork); const updateActiveSession = useAppSessionsStore(state => state.updateActiveSession); const addSession = useAppSessionsStore(state => state.addSession); @@ -382,8 +379,6 @@ const HomePanel = ({ const dispatch = useDispatch(); const { navigate } = useNavigation(); - const swapsV2Enabled = useExperimentalFlag(SWAPS_V2); - const actionButtonList = useMemo(() => { const walletIcon = selectedWallet?.IconComponent || <>; const walletLabel = selectedWallet?.label || ''; @@ -444,57 +439,27 @@ const HomePanel = ({ const valid = await runWalletChecksBeforeSwapOrBridge(); if (!valid) return; - const { swaps_v2 } = getRemoteConfig(); - - if (swaps_v2 || swapsV2Enabled) { - swapsStore.setState({ - inputAsset: userAssetsStore.getState().getHighestValueEth(), - }); - InteractionManager.runAfterInteractions(() => { - navigate(Routes.SWAP); - }); - return; - } - - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); - Navigation.handleAction(Routes.EXCHANGE_MODAL, { - fromDiscover: true, - params: { - inputAsset: mainnetEth, - }, - screen: Routes.MAIN_EXCHANGE_SCREEN, + swapsStore.setState({ + inputAsset: userAssetsStore.getState().getHighestValueEth(), + }); + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); }); - }, [navigate, runWalletChecksBeforeSwapOrBridge, selectedWallet?.uniqueId, swapsV2Enabled]); + }, [navigate, runWalletChecksBeforeSwapOrBridge]); const handleOnPressBridge = useCallback(async () => { const valid = await runWalletChecksBeforeSwapOrBridge(); if (!valid) return; - const { swaps_v2 } = getRemoteConfig(); - - if (swaps_v2 || swapsV2Enabled) { - // TODO: We need to set something in swapsStore that deliniates between a swap and bridge - // for now let's just treat it like a normal swap - swapsStore.setState({ - inputAsset: userAssetsStore.getState().getHighestValueEth(), - }); - InteractionManager.runAfterInteractions(() => { - navigate(Routes.SWAP); - }); - return; - } - - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); - Navigation.handleAction(Routes.EXCHANGE_MODAL, { - fromDiscover: true, - params: { - inputAsset: mainnetEth, - }, - screen: Routes.MAIN_EXCHANGE_SCREEN, + swapsStore.setState({ + inputAsset: userAssetsStore.getState().getHighestValueEth(), + }); + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); }); - }, [navigate, runWalletChecksBeforeSwapOrBridge, selectedWallet?.uniqueId, swapsV2Enabled]); + }, [navigate, runWalletChecksBeforeSwapOrBridge]); - const isOnHomepage = useBrowserStore(state => (state.getActiveTabUrl() || DEFAULT_TAB_URL) === RAINBOW_HOME); + const isOnHomepage = useBrowserStore(state => (state.getActiveTabUrl() || RAINBOW_HOME) === RAINBOW_HOME); return ( diff --git a/src/components/DappBrowser/search-input/AccountIcon.tsx b/src/components/DappBrowser/search-input/AccountIcon.tsx index 823a00feb0a..9bf8cc16b5d 100644 --- a/src/components/DappBrowser/search-input/AccountIcon.tsx +++ b/src/components/DappBrowser/search-input/AccountIcon.tsx @@ -13,7 +13,7 @@ import { getDappHost } from '../handleProviderRequest'; import { ButtonPressAnimation } from '@/components/animations'; import { useBrowserStore } from '@/state/browser/browserStore'; import { useBrowserContext } from '../BrowserContext'; -import { DEFAULT_TAB_URL, RAINBOW_HOME } from '../constants'; +import { RAINBOW_HOME } from '../constants'; export const AccountIcon = React.memo(function AccountIcon() { const { navigate } = useNavigation(); @@ -23,8 +23,8 @@ export const AccountIcon = React.memo(function AccountIcon() { const [currentAddress, setCurrentAddress] = useState(accountAddress); const { activeTabRef } = useBrowserContext(); - const activeTabHost = useBrowserStore(state => getDappHost(state.getActiveTabUrl())) || DEFAULT_TAB_URL; - const isOnHomepage = useBrowserStore(state => (state.getActiveTabUrl() || DEFAULT_TAB_URL) === RAINBOW_HOME); + const activeTabHost = useBrowserStore(state => getDappHost(state.getActiveTabUrl())) || RAINBOW_HOME; + const isOnHomepage = useBrowserStore(state => (state.getActiveTabUrl() || RAINBOW_HOME) === RAINBOW_HOME); const hostSessions = useAppSessionsStore(state => state.getActiveSession({ host: activeTabHost })); const currentSession = useMemo( () => diff --git a/src/components/exchange/ExchangeAssetList.tsx b/src/components/ExchangeAssetList.tsx similarity index 91% rename from src/components/exchange/ExchangeAssetList.tsx rename to src/components/ExchangeAssetList.tsx index ff7da7c343e..c22c58e1ca3 100644 --- a/src/components/exchange/ExchangeAssetList.tsx +++ b/src/components/ExchangeAssetList.tsx @@ -12,29 +12,39 @@ import React, { } from 'react'; import { InteractionManager, Keyboard, SectionList, SectionListData } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; -import { ButtonPressAnimation } from '../animations'; -import useAccountSettings from '../../hooks/useAccountSettings'; -import FastCurrencySelectionRow from '../asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; -import { ContactRow } from '../contacts'; -import DiscoverSheetContext from '../../screens/discover/DiscoverScreenContext'; -import { GradientText } from '../text'; -import { CopyToast, ToastPositionContainer } from '../toasts'; -import contextMenuProps from './exchangeAssetRowContextMenuProps'; +import { ButtonPressAnimation } from '@/components/animations'; +import useAccountSettings from '@/hooks/useAccountSettings'; +import FastCurrencySelectionRow from '@/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; +import { ContactRow } from '@/components/contacts'; +import DiscoverSheetContext from '@/screens/discover/DiscoverScreenContext'; +import { GradientText } from '@/components/text'; +import { CopyToast, ToastPositionContainer } from '@/components/toasts'; +import contextMenuProps from '@/components/exchangeAssetRowContextMenuProps'; import { TokenSectionTypes } from '@/helpers'; import { useAndroidScrollViewGestureHandler, usePrevious } from '@/hooks'; import { useNavigation } from '@/navigation'; -import store from '@/redux/store'; import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { abbreviations, deviceUtils, haptics, magicMemo } from '@/utils'; import { Box, Text } from '@/design-system'; import { colors, Colors } from '@/styles'; -import { EnrichedExchangeAsset } from '@/screens/CurrencySelectModal'; -import ExchangeTokenRow from './ExchangeTokenRow'; +import ExchangeTokenRow from '@/components/ExchangeTokenRow'; import { SwappableAsset } from '@/entities'; import { toggleFavorite, useFavorites } from '@/resources/favorites'; +export interface EnrichedExchangeAsset extends SwappableAsset { + ens: boolean; + color: string; + nickname: string; + onPress: (el: ReactElement) => void; + testID: string; + useGradientText: boolean; + title?: string; + key: string; + disabled?: boolean; +} + const deviceWidth = deviceUtils.dimensions.width; const HeaderBackground = styled(LinearGradient).attrs(({ theme: { colors } }: { theme: { colors: Colors } }) => ({ diff --git a/src/components/exchange/ExchangeHeader.tsx b/src/components/ExchangeHeader.tsx similarity index 81% rename from src/components/exchange/ExchangeHeader.tsx rename to src/components/ExchangeHeader.tsx index 78fe69639e1..4b83588ebe1 100644 --- a/src/components/exchange/ExchangeHeader.tsx +++ b/src/components/ExchangeHeader.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { SheetHandle } from '../sheet'; +import { SheetHandle } from '@/components/sheet'; import { Box, Text, Inset, Stack } from '@/design-system'; interface ExchangeHeaderProps { @@ -7,7 +7,7 @@ interface ExchangeHeaderProps { title: string; } -export default function ExchangeHeader({ testID, title }: ExchangeHeaderProps) { +export function ExchangeHeader({ testID, title }: ExchangeHeaderProps) { return ( diff --git a/src/components/exchange/ExchangeInput.js b/src/components/ExchangeInput.js similarity index 98% rename from src/components/exchange/ExchangeInput.js rename to src/components/ExchangeInput.js index baa013b087f..5c53792b5d9 100644 --- a/src/components/exchange/ExchangeInput.js +++ b/src/components/ExchangeInput.js @@ -1,7 +1,7 @@ import React, { Fragment, useCallback, useState } from 'react'; import { InteractionManager } from 'react-native'; import TextInputMask from 'react-native-text-input-mask'; -import { Text } from '../text'; +import { Text } from '@/components/text'; import styled from '@/styled-thing'; import { buildTextStyles } from '@/styles'; import { magicMemo } from '@/utils'; diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/ExchangeTokenRow.tsx similarity index 93% rename from src/components/exchange/ExchangeTokenRow.tsx rename to src/components/ExchangeTokenRow.tsx index e4fec0e9044..2b70fe2d35a 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/ExchangeTokenRow.tsx @@ -3,12 +3,12 @@ import isEqual from 'react-fast-compare'; import { Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; import { isNativeAsset } from '@/handlers/assets'; import { useAsset, useDimensions } from '@/hooks'; -import { ButtonPressAnimation } from '../animations'; -import { FloatingEmojis } from '../floating-emojis'; +import { ButtonPressAnimation } from '@/components/animations'; +import { FloatingEmojis } from '@/components/floating-emojis'; import { IS_IOS } from '@/env'; -import { FavStar, Info } from '../asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; +import { FavStar, Info } from '@/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; import { View } from 'react-native'; -import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ChainId } from '@/chains/types'; import { ParsedAddressAsset } from '@/entities'; diff --git a/src/components/expanded-state/custom-gas/FeesGweiInput.tsx b/src/components/FeesGweiInput.tsx similarity index 97% rename from src/components/expanded-state/custom-gas/FeesGweiInput.tsx rename to src/components/FeesGweiInput.tsx index 61eb40753db..89ddbea223c 100644 --- a/src/components/expanded-state/custom-gas/FeesGweiInput.tsx +++ b/src/components/FeesGweiInput.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import { ButtonPressAnimation } from '../../animations'; -import GweiInputPill from './GweiInputPill'; +import { ButtonPressAnimation } from '@/components/animations'; +import GweiInputPill from '@/components/GweiInputPill'; import { delay } from '@/helpers/utilities'; import { usePrevious } from '@/hooks'; import { TextInput } from 'react-native'; diff --git a/src/components/expanded-state/custom-gas/FeesPanel.tsx b/src/components/FeesPanel.tsx similarity index 97% rename from src/components/expanded-state/custom-gas/FeesPanel.tsx rename to src/components/FeesPanel.tsx index de4f837c369..c320cc89576 100644 --- a/src/components/expanded-state/custom-gas/FeesPanel.tsx +++ b/src/components/FeesPanel.tsx @@ -4,10 +4,10 @@ import { upperFirst } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { InteractionManager, Keyboard, KeyboardAvoidingView } from 'react-native'; import { IS_TESTING } from 'react-native-dotenv'; -import { Alert } from '../../alerts'; -import { useTheme } from '../../../theme/ThemeContext'; -import { ButtonPressAnimation } from '../../animations'; -import FeesGweiInput from './FeesGweiInput'; +import { Alert } from '@/components/alerts'; +import { useTheme } from '@/theme/ThemeContext'; +import { ButtonPressAnimation } from '@/components/animations'; +import FeesGweiInput from '@/components/FeesGweiInput'; import { calculateMinerTipAddDifference, calculateMinerTipSubstDifference } from '@/helpers/gas'; import { add, greaterThan, isZero, lessThan, multiply, toFixedDecimals } from '@/helpers/utilities'; import { useFeesPanelInputRefs, useGas, usePrevious, useTimeout } from '@/hooks'; @@ -156,7 +156,6 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack as={ButtonPressAnimation} paddingVertical="8px" marginVertical="-8px" - // @ts-ignore overloaded props onPress={openHelper} backgroundColor="accent" style={{ maxWidth: 175 }} @@ -546,15 +545,7 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack - openGasHelper(trendType)} - scaleTo={1} - > + openGasHelper(trendType)} scaleTo={1}> {renderRowLabel(lang.t('gas.current_base_fee'), trendType)} - openGasHelper(trendType)} - scaleTo={1} - > + openGasHelper(trendType)} scaleTo={1}> {formattedBaseFee} diff --git a/src/components/expanded-state/custom-gas/FeesPanelTabs.tsx b/src/components/FeesPanelTabs.tsx similarity index 97% rename from src/components/expanded-state/custom-gas/FeesPanelTabs.tsx rename to src/components/FeesPanelTabs.tsx index df49764c4e8..3298cc22348 100644 --- a/src/components/expanded-state/custom-gas/FeesPanelTabs.tsx +++ b/src/components/FeesPanelTabs.tsx @@ -1,6 +1,6 @@ import { isEmpty } from 'lodash'; import React from 'react'; -import { ButtonPressAnimation } from '../../animations'; +import { ButtonPressAnimation } from '@/components/animations'; import { useGas } from '@/hooks'; import { colors } from '@/styles'; import { gasUtils } from '@/utils'; diff --git a/src/components/expanded-state/custom-gas/GweiInputPill.tsx b/src/components/GweiInputPill.tsx similarity index 96% rename from src/components/expanded-state/custom-gas/GweiInputPill.tsx rename to src/components/GweiInputPill.tsx index 59e49424706..a1f9657de32 100644 --- a/src/components/expanded-state/custom-gas/GweiInputPill.tsx +++ b/src/components/GweiInputPill.tsx @@ -1,9 +1,9 @@ import React, { useCallback } from 'react'; import { IS_TESTING } from 'react-native-dotenv'; import LinearGradient from 'react-native-linear-gradient'; -// @ts-expect-error +// @ts-expect-error - no declaration file import TextInputMask from 'react-native-text-input-mask'; -import { ButtonPressAnimation } from '../../animations'; +import { ButtonPressAnimation } from '@/components/animations'; import styled from '@/styled-thing'; import { buildTextStyles, margin, padding } from '@/styles'; import { useTheme } from '@/theme'; diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx index 0924ee1c320..7a34dd140fd 100644 --- a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx +++ b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx @@ -7,7 +7,7 @@ import { ButtonPressAnimation } from '@/components/animations'; import { deviceUtils } from '@/utils'; import Routes from '@/navigation/routesNames'; import { ExtendedState } from './core/RawRecyclerList'; -import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/helpers/utilities'; import { analyticsV2 } from '@/analytics'; export const Claimable = React.memo(function Claimable({ uniqueId, extendedState }: { uniqueId: string; extendedState: ExtendedState }) { diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index b5200222c2e..a1ae36ea082 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -5,12 +5,11 @@ import { InteractionManager, PressableProps } from 'react-native'; import Animated, { useAnimatedStyle, useDerivedValue, withSpring } from 'react-native-reanimated'; import { ButtonPressAnimation } from '@/components/animations'; import { CopyFloatingEmojis } from '@/components/floating-emojis'; -import { enableActionsOnReadOnlyWallet, useExperimentalFlag, SWAPS_V2 } from '@/config'; +import { enableActionsOnReadOnlyWallet } from '@/config'; import { AccentColorProvider, Box, Column, Columns, Inset, Stack, Text, useColorMode } from '@/design-system'; -import { useAccountProfile, useAccountSettings, useWallets } from '@/hooks'; -import { delayNext } from '@/hooks/useMagicAutofocus'; +import { useAccountProfile, useWallets } from '@/hooks'; import { useNavigation } from '@/navigation'; -import { ethereumUtils, watchingAlert } from '@/utils'; +import { watchingAlert } from '@/utils'; import Routes from '@rainbow-me/routes'; import showWalletErrorAlert from '@/helpers/support'; import { analytics } from '@/analytics'; @@ -20,7 +19,6 @@ import { useAccountAccentColor } from '@/hooks/useAccountAccentColor'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { ChainId } from '@/chains/types'; export const ProfileActionButtonsRowHeight = 80; @@ -169,9 +167,6 @@ function BuyButton() { function SwapButton() { const { isReadOnlyWallet } = useWallets(); - const { accountAddress } = useAccountSettings(); - const remoteConfig = useRemoteConfig(); - const swapsV2Enabled = useExperimentalFlag(SWAPS_V2) || remoteConfig.swaps_v2; const { navigate } = useNavigation(); const handlePress = React.useCallback(async () => { @@ -179,30 +174,16 @@ function SwapButton() { analytics.track('Tapped "Swap"', { category: 'home screen', }); - if (swapsV2Enabled) { - swapsStore.setState({ - inputAsset: userAssetsStore.getState().getHighestValueEth(), - }); - InteractionManager.runAfterInteractions(() => { - navigate(Routes.SWAP); - }); - return; - } - - android && delayNext(); - - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: accountAddress }); - navigate(Routes.EXCHANGE_MODAL, { - fromDiscover: true, - params: { - inputAsset: mainnetEth, - }, - screen: Routes.MAIN_EXCHANGE_SCREEN, + swapsStore.setState({ + inputAsset: userAssetsStore.getState().getHighestValueEth(), + }); + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); }); } else { watchingAlert(); } - }, [accountAddress, isReadOnlyWallet, navigate, swapsV2Enabled]); + }, [isReadOnlyWallet, navigate]); return ( diff --git a/src/components/backup/BackupCloudStep.tsx b/src/components/backup/BackupCloudStep.tsx index e839d9323c0..cb0c4dde868 100644 --- a/src/components/backup/BackupCloudStep.tsx +++ b/src/components/backup/BackupCloudStep.tsx @@ -11,7 +11,7 @@ import { Text } from '@/components/text'; import WalletAndBackup from '@/assets/WalletsAndBackup.png'; import { analytics } from '@/analytics'; import { cloudBackupPasswordMinLength, isCloudBackupPasswordValid } from '@/handlers/cloudBackup'; -import { useDimensions, useMagicAutofocus, useWallets } from '@/hooks'; +import { useDimensions, useMagicAutofocus } from '@/hooks'; import styled from '@/styled-thing'; import { padding } from '@/styles'; import { Box, Inset, Stack } from '@/design-system'; @@ -23,9 +23,6 @@ import { usePasswordValidation } from './usePasswordValidation'; import { TextInput } from 'react-native'; import { useTheme } from '@/theme'; import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes'; -import walletTypes from '@/helpers/walletTypes'; type BackupCloudStepParams = { BackupCloudStep: { diff --git a/src/components/buttons/TokenSelectionButton.js b/src/components/buttons/TokenSelectionButton.js deleted file mode 100644 index 88ca2955713..00000000000 --- a/src/components/buttons/TokenSelectionButton.js +++ /dev/null @@ -1,83 +0,0 @@ -import lang from 'i18n-js'; -import React, { useMemo } from 'react'; -import { useTheme } from '../../theme/ThemeContext'; -import { ButtonPressAnimation } from '../animations'; -import { InnerBorder, RowWithMargins } from '../layout'; -import { TruncatedText } from '../text'; -import CaretImageSource from '@/assets/family-dropdown-arrow.png'; -import { ImgixImage } from '@/components/images'; -import styled from '@/styled-thing'; -import { padding, position } from '@/styles'; -import ShadowStack from '@/react-native-shadow-stack'; - -const TokenSelectionButtonHeight = 46; -const TokenSelectionButtonMaxWidth = 130; -const TokenSelectionButtonElevation = ios ? 0 : 8; - -const Content = styled(RowWithMargins).attrs({ - align: 'center', - margin: 7, -})({ - ...padding.object(11.5, 14, 13.5, 16), - height: TokenSelectionButtonHeight, - zIndex: 1, -}); - -const CaretIcon = styled(ImgixImage).attrs(({ theme: { colors } }) => ({ - resizeMode: ImgixImage.resizeMode.contain, - source: CaretImageSource, - tintColor: colors.whiteLabel, - size: 30, -}))({ - height: 18, - top: 0.5, - width: 8, -}); - -export default function TokenSelectionButton({ color, borderRadius = 30, onPress, symbol, testID }) { - const { isDarkMode, colors } = useTheme(); - - const shadowsForAsset = useMemo( - () => [ - [0, 10, 30, colors.shadow, 0.2], - [0, 5, 15, color, isDarkMode ? 0 : 0.4], - ], - [color, colors.shadow, isDarkMode] - ); - - return ( - - - - - {symbol ?? lang.t('swap.choose_token')} - - - - - - ); -} diff --git a/src/components/buttons/index.js b/src/components/buttons/index.js index 5541b1af74b..7ab8def660f 100644 --- a/src/components/buttons/index.js +++ b/src/components/buttons/index.js @@ -4,4 +4,3 @@ export { HoldToAuthorizeButton, HoldToAuthorizeButtonIcon } from './hold-to-auth export { default as MiniButton } from './MiniButton'; export { default as PasteAddressButton } from './PasteAddressButton'; export { default as RainbowButton } from './rainbow-button/RainbowButton'; -export { default as TokenSelectionButton } from './TokenSelectionButton'; diff --git a/src/components/cards/CarouselCard.tsx b/src/components/cards/CarouselCard.tsx index 7745ae27d8e..fe74acaa4bf 100644 --- a/src/components/cards/CarouselCard.tsx +++ b/src/components/cards/CarouselCard.tsx @@ -156,7 +156,7 @@ const RefreshButton = ({ refresh, isRefreshing, dataUpdatedAt }: RefreshButtonPr const { colorMode } = useColorMode(); const [canRefresh, setCanRefresh] = useState(dataUpdatedAt < Date.now() - 30_000); - const interval = useRef(); + const interval = useRef>(); useEffect(() => { const checkRefresh = () => { diff --git a/src/components/coin-row/CoinRowInfoButton.js b/src/components/coin-row/CoinRowInfoButton.js deleted file mode 100644 index c9153d1a1fa..00000000000 --- a/src/components/coin-row/CoinRowInfoButton.js +++ /dev/null @@ -1,162 +0,0 @@ -import lang from 'i18n-js'; -import { startCase } from 'lodash'; -import React from 'react'; -import { View } from 'react-native'; -import { IS_TESTING } from 'react-native-dotenv'; -import { ContextMenuButton } from 'react-native-ios-context-menu'; -import RadialGradient from 'react-native-radial-gradient'; -import { ButtonPressAnimation } from '../animations'; -import { Centered } from '../layout'; -import { Text } from '../text'; -import { CoinRowHeight } from './CoinRow'; -import { useClipboard } from '@/hooks'; -import styled from '@/styled-thing'; -import { fonts, fontWithWidth, padding } from '@/styles'; -import { abbreviations, ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils'; - -const InfoButton = styled(Centered)({ - ...padding.object(8, 0), - alignItems: 'center', - bottom: 0, - flex: 0, - height: CoinRowHeight, - justifyContent: 'center', - position: 'absolute', - right: ({ showFavoriteButton }) => (showFavoriteButton ? 40 : 0), - top: 0, - width: 68, -}); - -const Circle = styled(IS_TESTING === 'true' ? View : RadialGradient).attrs(({ theme: { colors } }) => ({ - center: [0, 15], - colors: colors.gradients.lightestGrey, -}))({ - borderRadius: 15, - height: 30, - margin: 10, - overflow: 'hidden', - width: 30, -}); - -const Icon = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.3), - letterSpacing: 'zero', - size: 'lmedium', - weight: 'bold', -}))({ - ...fontWithWidth(fonts.weight.bold), - height: '100%', - lineHeight: 30, - width: '100%', -}); - -const CoinRowActionsEnum = { - blockExplorer: 'blockExplorer', - copyAddress: 'copyAddress', -}; - -const CoinRowActions = { - [CoinRowActionsEnum.copyAddress]: { - actionKey: CoinRowActionsEnum.copyAddress, - actionTitle: lang.t('wallet.action.copy_contract_address'), - icon: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, - }, -}; - -const buildBlockExplorerAction = chainId => { - const blockExplorerText = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), - }); - return { - actionKey: CoinRowActionsEnum.blockExplorer, - actionTitle: blockExplorerText, - icon: { - iconType: 'SYSTEM', - iconValue: 'link', - }, - }; -}; - -const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) => { - const { setClipboard } = useClipboard(); - const handleCopyContractAddress = useCallback( - address => { - haptics.selection(); - setClipboard(address); - onCopySwapDetailsText(address); - }, - [onCopySwapDetailsText, setClipboard] - ); - - const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId: item?.chainId }))}`; - const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; - - showActionSheetWithOptions( - { - cancelButtonIndex: 2, - options: androidContractActions, - showSeparators: true, - title: `${item?.name} (${item?.symbol})`, - }, - idx => { - if (idx === 0) { - handleCopyContractAddress(item?.address); - } - if (idx === 1) { - ethereumUtils.openTokenEtherscanURL({ address: item?.address, chainId: item?.chainId }); - } - } - ); - }, [item, handleCopyContractAddress]); - - const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(item?.chainId); - return { - menuItems: [ - blockExplorerAction, - { - ...CoinRowActions[CoinRowActionsEnum.copyAddress], - discoverabilityTitle: abbreviations.formatAddressForDisplay(item?.address), - }, - ], - menuTitle: `${item?.name} (${item?.symbol})`, - }; - }, [item?.address, item?.chainId, item?.name, item?.symbol]); - - const handlePressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }) => { - if (actionKey === CoinRowActionsEnum.copyAddress) { - handleCopyContractAddress(item?.address); - } else if (actionKey === CoinRowActionsEnum.blockExplorer) { - ethereumUtils.openTokenEtherscanURL({ address: item?.address, chainId: item?.chainId }); - } - }, - [item, handleCopyContractAddress] - ); - return ( - - - - - 􀅳 - - - - - ); -}; - -export default CoinRowInfoButton; diff --git a/src/components/coin-row/FastTransactionCoinRow.tsx b/src/components/coin-row/FastTransactionCoinRow.tsx index fdc2b5b8866..9d1758037d0 100644 --- a/src/components/coin-row/FastTransactionCoinRow.tsx +++ b/src/components/coin-row/FastTransactionCoinRow.tsx @@ -23,8 +23,8 @@ import { import { TwoCoinsIcon } from '../coin-icon/TwoCoinsIcon'; import Spinner from '../Spinner'; import * as lang from '@/languages'; -import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; -import { checkForPendingSwap } from '@/screens/transaction-details/helpers/checkForPendingSwap'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { checkForPendingSwap } from '@/helpers/checkForPendingSwap'; export const getApprovalLabel = ({ approvalAmount, asset, type }: Pick) => { if (!approvalAmount || !asset) return; diff --git a/src/components/discover/DiscoverSearchInput.js b/src/components/discover/DiscoverSearchInput.js index 7d82759befe..affbc4bf629 100644 --- a/src/components/discover/DiscoverSearchInput.js +++ b/src/components/discover/DiscoverSearchInput.js @@ -15,27 +15,27 @@ import { deviceUtils } from '@/utils'; import DiscoverSheetContext from '@/screens/discover/DiscoverScreenContext'; import { chainsName } from '@/chains'; -export const ExchangeSearchHeight = 40; -const ExchangeSearchWidth = deviceUtils.dimensions.width - 30; +const SearchHeight = 40; +const SearchWidth = deviceUtils.dimensions.width - 30; const Container = styled(Row)(({ isSearchModeEnabled, theme: { colors } }) => ({ ...margin.object(0, 15, isSearchModeEnabled ? 8 : 0), ...(isSearchModeEnabled ? padding.object(0, 37, 0, 12) : padding.object(0)), backgroundColor: colors.transparent, - borderRadius: ExchangeSearchHeight / 2, - height: ExchangeSearchHeight, + borderRadius: SearchHeight / 2, + height: SearchHeight, overflow: 'hidden', })); const BackgroundGradient = styled(RadialGradient).attrs(({ isDiscover, theme: { colors } }) => ({ - center: [ExchangeSearchWidth, ExchangeSearchWidth / 2], + center: [SearchWidth, SearchWidth / 2], colors: isDiscover ? colors.gradients.searchBar : colors.gradients.lightGreyTransparent, }))({ - height: ExchangeSearchWidth, + height: SearchWidth, position: 'absolute', - top: -(ExchangeSearchWidth - ExchangeSearchHeight) / 2, - transform: [{ scaleY: ExchangeSearchHeight / ExchangeSearchWidth }], - width: ExchangeSearchWidth, + top: -(SearchWidth - SearchHeight) / 2, + transform: [{ scaleY: SearchHeight / SearchWidth }], + width: SearchWidth, }); const SearchIcon = styled(Text).attrs(({ theme: { colors } }) => ({ @@ -98,7 +98,7 @@ const timingConfig = { duration: 300, }; -const ExchangeSearch = ( +const DiscoverSearchInput = ( { isDiscover, isFetching, @@ -188,7 +188,7 @@ const ExchangeSearch = ( value={searchQuery} /> null; - -export default function ConfirmExchangeButton({ - chainId, - disabled, - loading, - isHighPriceImpact, - quoteError, - onPressViewDetails, - onSubmit, - testID, - tradeDetails, - type = ExchangeModalTypes.swap, - isSufficientBalance, - isBridgeSwap, - ...props -}) { - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - const asset = outputCurrency ?? inputCurrency; - const { isSufficientGas, isValidGas, isGasReady } = useGas(); - const { name: routeName } = useRoute(); - const { navigate } = useNavigation(); - const [isSwapSubmitting, setIsSwapSubmitting] = useState(false); - const { isHardwareWallet } = useWallets(); - - const isSwapDetailsRoute = routeName === Routes.SWAP_DETAILS_SHEET; - const shouldOpenSwapDetails = tradeDetails && !isSwapDetailsRoute; - - const { colors, isDarkMode } = useTheme(); - - const shadows = useMemo( - () => ({ - default: [[0, 10, 30, darkModeThemeColors.shadow, 0.4]], - disabled: [ - [0, 10, 30, colors.shadow, isDarkMode ? 0 : 0.2], - [0, 5, 15, isDarkMode ? colors.shadow : lightModeThemeColors.blueGreyDark50, 0.4], - ], - }), - [colors, isDarkMode] - ); - - const colorForAsset = useColorForAsset(asset, undefined, true, true); - - const disabledButtonColor = isSwapDetailsRoute - ? isDarkMode - ? darkModeThemeColors.blueGreyDark04 - : lightModeThemeColors.blueGreyDark50 - : darkModeThemeColors.blueGreyDark04; - - const { buttonColor, shadowsForAsset } = useMemo(() => { - const color = quoteError - ? disabledButtonColor - : asset.address === ETH_ADDRESS - ? colors.appleBlue - : isSwapDetailsRoute - ? colorForAsset - : makeColorMoreChill(colorForAsset, (isSwapDetailsRoute ? colors : darkModeThemeColors).light); - - return { - buttonColor: color, - shadowsForAsset: [ - [0, 10, 30, colors.shadow, 0.2], - [0, 5, 15, isDarkMode ? colors.trueBlack : color, 0.4], - ], - }; - }, [asset.address, colorForAsset, colors, disabledButtonColor, quoteError, isDarkMode, isSwapDetailsRoute]); - - let label = ''; - let explainerType = null; - - if (type === ExchangeModalTypes.swap) { - label = `􀕹 ${lang.t('button.confirm_exchange.review')}`; - } - if (loading) { - label = lang.t('button.confirm_exchange.fetching_quote'); - } else if (!isSufficientBalance) { - label = lang.t('button.confirm_exchange.insufficient_funds'); - } else if (isSufficientGas != null && !isSufficientGas) { - label = lang.t('button.confirm_exchange.insufficient_token', { - tokenName: chainsNativeAsset[chainId].symbol, - }); - } else if (!isValidGas && isGasReady) { - label = lang.t('button.confirm_exchange.invalid_fee'); - } else if (isSwapDetailsRoute) { - if (isSwapSubmitting) { - label = lang.t('button.confirm_exchange.submitting'); - } else if (isBridgeSwap) { - label = `${lang.t('button.confirm_exchange.bridge')}`; - } else { - label = isHighPriceImpact ? lang.t('button.confirm_exchange.swap_anyway') : `${lang.t('button.confirm_exchange.swap')}`; - } - } else if (disabled) { - label = lang.t('button.confirm_exchange.enter_amount'); - } - - if (quoteError) { - const error = handleSwapErrorCodes(quoteError); - label = error.buttonLabel; - explainerType = error.explainerType; - } - - const handleExplainer = useCallback(() => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - inputCurrency, - outputCurrency, - type: explainerType, - }); - }, [explainerType, inputCurrency, navigate, outputCurrency]); - - const isDisabled = disabled || !isSufficientBalance || !isSufficientGas || !isValidGas || !isSufficientGas; - - const onSwap = useCallback(async () => { - setIsSwapSubmitting(true); - const submitted = await onSubmit(setIsSwapSubmitting); - setIsSwapSubmitting(submitted); - }, [onSubmit]); - - return ( - - - - - - - - ); -} diff --git a/src/components/exchange/CurrencySelectModalHeader.tsx b/src/components/exchange/CurrencySelectModalHeader.tsx deleted file mode 100644 index 204946d9c4e..00000000000 --- a/src/components/exchange/CurrencySelectModalHeader.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import React, { useCallback } from 'react'; -import { delayNext } from '../../hooks/useMagicAutofocus'; -import { BackButton } from '../header'; -import { SheetHandleFixedToTop } from '../sheet'; -import { Box, Inset, Text } from '@/design-system'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; -import { useTheme } from '@/theme'; -import { chainsIdByName } from '@/chains'; - -export const CurrencySelectModalHeaderHeight = 59; - -export default function CurrencySelectModalHeader({ - handleBackButton, - showBackButton, - showHandle, - testID, -}: { - handleBackButton: () => void; - showBackButton: boolean; - showHandle: boolean; - testID: string; -}) { - const { navigate, getState: dangerouslyGetState } = useNavigation(); - const { - params: { defaultOutputAsset, title, showCoinIcon }, - } = useRoute(); - const theme = useTheme(); - - const handlePressBack = useCallback(() => { - // @ts-expect-error – updating read-only property - dangerouslyGetState().index = 1; - delayNext(); - handleBackButton(); - navigate(Routes.MAIN_EXCHANGE_SCREEN); - }, [dangerouslyGetState, handleBackButton, navigate]); - - return ( - - {showHandle && } - {showBackButton && ( - - {/** @ts-expect-error JavaScript component */} - - - )} - {showCoinIcon && ( - - - - )} - - - {title} - - - ); -} diff --git a/src/components/exchange/ExchangeDetailsButton.js b/src/components/exchange/ExchangeDetailsButton.js deleted file mode 100644 index 049c2768f3e..00000000000 --- a/src/components/exchange/ExchangeDetailsButton.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { ButtonPressAnimation } from '../animations'; -import { Text } from '../text'; -import styled from '@/styled-thing'; -import { lightModeThemeColors, padding } from '@/styles'; - -const ExchangeDetailsButtonLabel = styled(Text).attrs({ - color: lightModeThemeColors.white, - size: 'large', - weight: 'heavy', - ...(android && { lineHeight: 21 }), -})({ - ...padding.object(9), -}); - -export default function ExchangeDetailsButton({ children, disabled, onPress, ...props }) { - return ( - - {children} - - ); -} diff --git a/src/components/exchange/ExchangeDetailsRow.tsx b/src/components/exchange/ExchangeDetailsRow.tsx deleted file mode 100644 index fa38179855e..00000000000 --- a/src/components/exchange/ExchangeDetailsRow.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import lang from 'i18n-js'; -import React, { useEffect } from 'react'; -import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; -import ExchangeDetailsButton from './ExchangeDetailsButton'; -import PriceImpactWarning from './PriceImpactWarning'; -import { analytics } from '@/analytics'; -import { Box } from '@/design-system'; -import { usePrevious, useSwapCurrencies } from '@/hooks'; - -const defaultPriceImpactScale = 1.15; -const timingConfig = { - duration: 200, - easing: Easing.bezier(0.76, 0, 0.24, 1), -}; - -interface ExchangeDetailsRowProps { - isHighPriceImpact: boolean; - onFlipCurrencies: () => void; - onPressSettings: () => void; - onPressImpactWarning: () => void; - priceImpactColor?: string; - priceImpactNativeAmount?: string | null; - priceImpactPercentDisplay?: string | null; - outputCurrencySymbol?: string | null; - type: string; -} - -export default function ExchangeDetailsRow({ - isHighPriceImpact, - onFlipCurrencies, - onPressSettings, - onPressImpactWarning, - priceImpactColor, - priceImpactNativeAmount, - priceImpactPercentDisplay, - outputCurrencySymbol, - type, -}: ExchangeDetailsRowProps) { - const detailsRowOpacity = useSharedValue(1); - const priceImpactOpacity = useSharedValue(0); - const priceImpactScale = useSharedValue(defaultPriceImpactScale); - const { outputCurrency } = useSwapCurrencies(); - - const detailsRowAnimatedStyle = useAnimatedStyle(() => ({ - opacity: detailsRowOpacity.value, - })); - - const priceImpactAnimatedStyle = useAnimatedStyle(() => ({ - opacity: priceImpactOpacity.value, - transform: [{ scale: priceImpactScale.value }], - })); - - const prevIsHighPriceImpact = usePrevious(isHighPriceImpact); - - useEffect(() => { - if (isHighPriceImpact && !prevIsHighPriceImpact) { - analytics.track('Showing high price impact warning in Swap', { - name: outputCurrency.name, - priceImpact: priceImpactPercentDisplay, - symbol: outputCurrency.symbol, - tokenAddress: outputCurrency.address, - type, - }); - } - }, [isHighPriceImpact, outputCurrency, prevIsHighPriceImpact, priceImpactPercentDisplay, type]); - - useEffect(() => { - if (isHighPriceImpact) { - detailsRowOpacity.value = withTiming(0, timingConfig); - priceImpactOpacity.value = withTiming(1, timingConfig); - priceImpactScale.value = withTiming(1, timingConfig); - } else { - detailsRowOpacity.value = withTiming(1, timingConfig); - priceImpactOpacity.value = withTiming(0, timingConfig); - priceImpactScale.value = withTiming(defaultPriceImpactScale, timingConfig); - } - }, [detailsRowOpacity, isHighPriceImpact, priceImpactOpacity, priceImpactScale]); - - return ( - - - - {/* @ts-expect-error - Javascript Component */} - - 􀄬 {lang.t('exchange.flip')} - - {/* @ts-expect-error - Javascript Component */} - - 􀣋 {lang.t('exchange.settings')} - - - - ); -} diff --git a/src/components/exchange/ExchangeField.tsx b/src/components/exchange/ExchangeField.tsx deleted file mode 100644 index 0123dc16ab4..00000000000 --- a/src/components/exchange/ExchangeField.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import React, { FocusEvent, ForwardRefRenderFunction, MutableRefObject, useCallback, useEffect, useState } from 'react'; -import { StyleProp, TextInput, TouchableWithoutFeedback, ViewStyle } from 'react-native'; -import { TokenSelectionButton } from '../buttons'; -import { ChainBadge, CoinIconSize } from '../coin-icon'; -import { EnDash } from '../text'; -import ExchangeInput from './ExchangeInput'; -import { ChainId } from '@/chains/types'; -import styled from '@/styled-thing'; -import { borders } from '@/styles'; -import { useTheme } from '@/theme'; -import { AccentColorProvider, Box, Space } from '@/design-system'; -import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; -import { TokenColors } from '@/graphql/__generated__/metadata'; - -const ExchangeFieldHeight = android ? 64 : 38; -const ExchangeFieldPadding: Space = android ? '15px (Deprecated)' : '19px (Deprecated)'; - -const Input = styled(ExchangeInput).attrs({ - letterSpacing: 'roundedTightest', -})({ - height: ExchangeFieldHeight + (android ? 20 : 0), - marginVertical: -10, -}); - -interface ExchangeFieldProps { - icon?: string; - colors?: TokenColors; - address: string; - color: string; - mainnetAddress?: string; - amount: string | null; - disableCurrencySelection?: boolean; - editable: boolean; - loading: boolean; - type?: string; - chainId: ChainId; - onBlur?: (event: FocusEvent) => void; - onFocus: (event: FocusEvent) => void; - onPressSelectCurrency: (chainId: any) => void; - onTapWhileDisabled?: () => void; - setAmount: (value: string | null) => void; - symbol?: string; - testID: string; - useCustomAndroidMask: boolean; - updateOnFocus: boolean; - style?: StyleProp; -} - -const ExchangeField: ForwardRefRenderFunction = ( - { - icon, - colors, - amount, - color, - disableCurrencySelection, - editable, - loading, - chainId, - onBlur, - onFocus, - onPressSelectCurrency, - onTapWhileDisabled, - setAmount, - symbol, - testID, - useCustomAndroidMask = false, - updateOnFocus = false, - style, - }, - ref -) => { - const inputRef = ref as MutableRefObject; - const theme = useTheme(); - const [value, setValue] = useState(amount); - - const handleFocusField = useCallback(() => { - inputRef?.current?.focus(); - }, [inputRef]); - - const handleBlur = useCallback( - // @ts-expect-error passed to an untyped JS component - event => { - onBlur?.(event); - }, - [onBlur] - ); - const handleFocus = useCallback( - // @ts-expect-error passed to an untyped JS component - event => { - onFocus?.(event); - if (loading) { - setAmount(value); - } - }, - [loading, onFocus, setAmount, value] - ); - - const onChangeText = useCallback( - // @ts-expect-error passed to an untyped JS component - text => { - setAmount(text); - setValue(text); - }, - [setAmount] - ); - - const placeholderTextColor = symbol - ? theme.colors.alpha(theme.colors.blueGreyDark, 0.3) - : theme.colors.alpha(theme.colors.blueGreyDark, 0.1); - - const placeholderText = symbol ? '0' : EnDash.unicode; - - const editing = inputRef?.current?.isFocused?.() ?? false; - - useEffect(() => { - if (!editing || updateOnFocus) { - setValue(amount); - } - }, [amount, editing, updateOnFocus]); - - return ( - - - - - {symbol ? ( - - ) : ( - - - - - - - )} - - - - - - {!disableCurrencySelection && ( - - )} - - ); -}; - -export default React.forwardRef(ExchangeField); diff --git a/src/components/exchange/ExchangeFloatingPanels.tsx b/src/components/exchange/ExchangeFloatingPanels.tsx deleted file mode 100644 index 5584e9c77ca..00000000000 --- a/src/components/exchange/ExchangeFloatingPanels.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { ReactNode } from 'react'; -import { View } from 'react-native'; -import { Box, Stack } from '@/design-system'; - -interface ExchangeFloatingPanelsProps { - children: ReactNode; -} - -const ExchangeFloatingPanels = React.forwardRef((props, ref) => { - const children = props.children; - return ( - - {children} - - ); -}); - -ExchangeFloatingPanels.displayName = 'ExchangeFloatingPanels'; - -export default ExchangeFloatingPanels; diff --git a/src/components/exchange/ExchangeInputField.tsx b/src/components/exchange/ExchangeInputField.tsx deleted file mode 100644 index c2dd322e4e2..00000000000 --- a/src/components/exchange/ExchangeInputField.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { MutableRefObject } from 'react'; -import { TextInput } from 'react-native'; -import { ColumnWithMargins, Row } from '../layout'; -import ExchangeField from './ExchangeField'; -import ExchangeMaxButton from './ExchangeMaxButton'; -import ExchangeNativeField from './ExchangeNativeField'; -import { ChainId } from '@/chains/types'; -import styled from '@/styled-thing'; -import { TokenColors } from '@/graphql/__generated__/metadata'; - -const Container = styled(ColumnWithMargins).attrs({ margin: 5 })({ - paddingTop: android ? 0 : 6, - width: '100%', - zIndex: 1, -}); - -const NativeFieldRow = styled(Row).attrs({ - align: 'center', - justify: 'space-between', -})({ - height: android ? 16 : 32, - paddingLeft: 19, -}); - -interface ExchangeInputFieldProps { - color: string; - disableInputCurrencySelection: boolean; - editable: boolean; - loading: boolean; - nativeAmount: string | null; - nativeCurrency: string; - nativeFieldRef: MutableRefObject; - chainId: ChainId; - onFocus: ({ target }: { target: Element }) => void; - onPressMaxBalance: () => void; - onPressSelectInputCurrency: (chainId: ChainId) => void; - inputAmount: string | null; - inputCurrencyIcon?: string; - inputCurrencyColors?: TokenColors; - inputCurrencyAddress: string; - inputCurrencyMainnetAddress?: string; - inputCurrencyNetwork?: string; - inputCurrencySymbol?: string; - inputFieldRef: MutableRefObject; - setInputAmount: (value: string | null) => void; - setNativeAmount: (value: string | null) => void; - updateAmountOnFocus: boolean; - testID: string; -} - -export default function ExchangeInputField({ - color, - disableInputCurrencySelection, - editable, - inputAmount, - inputCurrencyAddress, - inputCurrencyMainnetAddress, - inputCurrencySymbol, - inputCurrencyIcon, - inputCurrencyColors, - inputFieldRef, - loading, - nativeAmount, - nativeCurrency, - nativeFieldRef, - chainId, - inputCurrencyNetwork, - onFocus, - onPressMaxBalance, - onPressSelectInputCurrency, - setInputAmount, - setNativeAmount, - testID, - updateAmountOnFocus, -}: ExchangeInputFieldProps) { - return ( - - - - - - - - ); -} diff --git a/src/components/exchange/ExchangeMaxButton.tsx b/src/components/exchange/ExchangeMaxButton.tsx deleted file mode 100644 index 548f79859a6..00000000000 --- a/src/components/exchange/ExchangeMaxButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import { ButtonPressAnimation } from '../animations'; -import { Box, Text } from '@/design-system'; -import styled from '@/styled-thing'; -import { useTheme } from '@/theme'; - -const Container = styled(ButtonPressAnimation)({ - marginRight: 4, -}); - -interface ExchangeMaxButtonProps { - color: string; - disabled: boolean; - onPress: () => void; - testID: string; -} - -export default function ExchangeMaxButton({ color, disabled, onPress, testID }: ExchangeMaxButtonProps) { - const { colors } = useTheme(); - - return ( - - - - 􀜍 {lang.t('exchange.max')} - - - - ); -} diff --git a/src/components/exchange/ExchangeNativeField.tsx b/src/components/exchange/ExchangeNativeField.tsx deleted file mode 100644 index 84acc1a3fb0..00000000000 --- a/src/components/exchange/ExchangeNativeField.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { ForwardRefRenderFunction, MutableRefObject, useCallback, useEffect, useMemo, useState } from 'react'; -import { TextInput, TouchableWithoutFeedback } from 'react-native'; -import { Row } from '../layout'; -import ExchangeInput from './ExchangeInput'; -import { supportedNativeCurrencies } from '@/references'; -import styled from '@/styled-thing'; -import { fonts } from '@/styles'; -import { useTheme } from '@/theme'; -import { Box, Text } from '@/design-system'; -import { NativeCurrencyKey } from '@/entities'; - -const NativeInput = styled(ExchangeInput).attrs({ - letterSpacing: fonts.letterSpacing.roundedTight, - size: fonts.size.larger, - weight: fonts.weight.regular, -})({ - height: ({ height }: { height: number }) => height, -}); - -interface ExchangeNativeFieldProps { - color: string; - editable: boolean; - height: number; - loading: boolean; - nativeAmount: string | null; - nativeCurrency: string; - onFocus: ({ target }: { target: Element }) => void; - setNativeAmount: (value: string | null) => void; - updateOnFocus: boolean; - testID: string; -} - -const ExchangeNativeField: ForwardRefRenderFunction = ( - { color, editable, height, loading, nativeAmount, nativeCurrency, onFocus, setNativeAmount, updateOnFocus, testID }, - ref -) => { - const nativeFieldRef = ref as MutableRefObject; - - const [value, setValue] = useState(nativeAmount); - - const { mask, placeholder, symbol } = supportedNativeCurrencies[nativeCurrency as NativeCurrencyKey]; - - const handleFocusNativeField = useCallback(() => nativeFieldRef?.current?.focus(), [nativeFieldRef]); - - const handleFocus = useCallback( - // @ts-expect-error passed to an untyped JS component - event => { - onFocus?.(event); - if (loading) { - setNativeAmount(value); - } - }, - [loading, onFocus, setNativeAmount, value] - ); - - const onChangeText = useCallback( - // @ts-expect-error passed to an untyped JS component - text => { - setNativeAmount(text); - setValue(text); - }, - [setNativeAmount] - ); - - const { colors } = useTheme(); - - const isFocused = nativeFieldRef?.current?.isFocused?.(); - - const nativeAmountColor = useMemo(() => { - const nativeAmountExists = typeof nativeAmount === 'string' && nativeAmount.length > 0; - - const color = isFocused ? colors.dark : colors.blueGreyDark; - const opacity = isFocused ? 1 : nativeAmountExists ? 0.5 : 0.3; - - return colors.alpha(color, opacity); - }, [colors, isFocused, nativeAmount]); - - useEffect(() => { - if (!isFocused || updateOnFocus) { - setValue(nativeAmount); - } - }, [nativeAmount, isFocused, updateOnFocus]); - - return ( - - - - - {symbol} - - - - - - ); -}; - -export default React.forwardRef(ExchangeNativeField); diff --git a/src/components/exchange/ExchangeNotch.tsx b/src/components/exchange/ExchangeNotch.tsx deleted file mode 100644 index 96bea8e3a4a..00000000000 --- a/src/components/exchange/ExchangeNotch.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import FastImage from 'react-native-fast-image'; -import ExchangeNotchLeft from '@/assets/exchangeNotchLeft.png'; -import ExchangeNotchLeftDark from '@/assets/exchangeNotchLeftDark.png'; -import ExchangeNotchMiddle from '@/assets/exchangeNotchMiddle.png'; -import ExchangeNotchMiddleDark from '@/assets/exchangeNotchMiddleDark.png'; -import ExchangeNotchRight from '@/assets/exchangeNotchRight.png'; -import ExchangeNotchRightDark from '@/assets/exchangeNotchRightDark.png'; -import { Box } from '@/design-system'; -import { useDimensions } from '@/hooks'; -import styled from '@/styled-thing'; -import { useTheme } from '@/theme'; - -const notchHeight = 48; -const notchSideWidth = 78; - -const NotchMiddle = styled(FastImage).attrs(({ isDarkMode }: { isDarkMode: boolean }) => ({ - resizeMode: FastImage.resizeMode.stretch, - source: isDarkMode ? ExchangeNotchMiddleDark : ExchangeNotchMiddle, -}))({ - height: notchHeight, - left: 0, - width: ({ deviceWidth }: { deviceWidth: number }) => deviceWidth - notchSideWidth * 2, -}); - -const NotchSide = styled(FastImage)({ - height: android ? notchHeight + 2 : notchHeight, - left: 0, - width: notchSideWidth, -}); - -interface ExchangeNotchProps { - testID: string; -} - -export default function ExchangeNotch({ testID }: ExchangeNotchProps) { - const { width: deviceWidth } = useDimensions(); - const { isDarkMode } = useTheme(); - return ( - - - - - - ); -} diff --git a/src/components/exchange/ExchangeOutputField.tsx b/src/components/exchange/ExchangeOutputField.tsx deleted file mode 100644 index c2913022edd..00000000000 --- a/src/components/exchange/ExchangeOutputField.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { MutableRefObject } from 'react'; -import { TextInput } from 'react-native'; -import ExchangeField from './ExchangeField'; -import { Box } from '@rainbow-me/design-system'; -import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/chains/types'; - -interface ExchangeOutputFieldProps { - color: string; - chainId: ChainId; - editable: boolean; - loading: boolean; - onFocus: ({ target }: { target: Element }) => void; - onPressSelectOutputCurrency: () => void; - onTapWhileDisabled?: () => void; - outputAmount: string | null; - outputCurrencyIcon?: string; - outputCurrencyColors?: TokenColors; - outputCurrencyAddress: string; - outputCurrencyMainnetAddress?: string; - outputCurrencyNetwork?: string; - outputCurrencySymbol?: string; - outputFieldRef: MutableRefObject; - setOutputAmount: (value: string | null) => void; - updateAmountOnFocus: boolean; - testID: string; -} - -export default function ExchangeOutputField({ - color, - editable, - loading, - chainId, - onFocus, - onPressSelectOutputCurrency, - onTapWhileDisabled, - outputAmount, - outputCurrencyIcon, - outputCurrencyColors, - outputCurrencyAddress, - outputCurrencyMainnetAddress, - outputCurrencySymbol, - outputFieldRef, - setOutputAmount, - updateAmountOnFocus, - testID, -}: ExchangeOutputFieldProps) { - return ( - - - - ); -} diff --git a/src/components/exchange/ExchangeSearch.tsx b/src/components/exchange/ExchangeSearch.tsx deleted file mode 100644 index 9f5302788ae..00000000000 --- a/src/components/exchange/ExchangeSearch.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import lang from 'i18n-js'; -import { isEmpty } from 'lodash'; -import React, { ForwardRefRenderFunction, MutableRefObject, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; -import LinearGradient from 'react-native-linear-gradient'; -import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'; -import Spinner from '../../assets/chartSpinner.png'; -import DiscoverSheetContext from '../../screens/discover/DiscoverScreenContext'; -import { ClearInputDecorator, Input } from '../inputs'; -import { analytics } from '@/analytics'; -import { ImgixImage } from '@/components/images'; -import styled from '@/styled-thing'; -import { margin, Colors } from '@/styles'; -import { deviceUtils } from '@/utils'; -import ShadowStack from '@/react-native-shadow-stack'; -import { TextInput } from 'react-native'; -import { useTheme } from '@/theme'; -import { Box, Text } from '@/design-system'; -import { Source } from 'react-native-fast-image'; -import { IS_TEST } from '@/env'; -import { ChainId } from '@rainbow-me/swaps'; - -export const ExchangeSearchHeight = 40; -const DoneButtonWidth = 52; -const ExchangeSearchWidth = deviceUtils.dimensions.width - 40; - -const ShadowContainer = styled(ShadowStack)(() => ({ - ...margin.object(0, 20, 20, 20), -})); - -const BackgroundGradient = styled(LinearGradient).attrs(({ theme: { colors } }: { theme: { colors: Colors } }) => ({ - colors: colors.gradients.offWhite, - end: { x: 0.5, y: 1 }, - start: { x: 0.5, y: 0 }, -}))({ - borderRadius: ExchangeSearchHeight / 2, - height: ExchangeSearchWidth, - left: 0, - overflow: 'hidden', - position: 'absolute', - top: -(ExchangeSearchWidth - ExchangeSearchHeight) / 2, - transform: [{ scaleY: ExchangeSearchHeight / ExchangeSearchWidth }], - width: ExchangeSearchWidth, -}); - -const SearchInput = styled(Input).attrs( - ({ - theme: { colors }, - isSearchModeEnabled, - clearTextOnFocus, - }: { - theme: { colors: Colors }; - isSearchModeEnabled: boolean; - clearTextOnFocus: boolean; - }) => ({ - blurOnSubmit: false, - clearTextOnFocus, - color: colors.alpha(colors.blueGreyDark, 0.8), - enablesReturnKeyAutomatically: true, - keyboardAppearance: 'dark', - keyboardType: 'ascii-capable', - lineHeight: 'looserLoose', - placeholderTextColor: colors.alpha(colors.blueGreyDark, 0.6), - returnKeyType: 'search', - selectionColor: isSearchModeEnabled ? colors.appleBlue : colors.transparent, - size: 'large', - spellCheck: false, - weight: 'semibold', - }) -)({ - ...(android ? { marginBottom: -10, marginTop: -6 } : {}), - flex: 1, - height: ios ? 39 : 56, - marginBottom: 1, - marginLeft: ({ isSearchModeEnabled }: { isSearchModeEnabled: boolean }) => (isSearchModeEnabled ? 4 : 0), - textAlign: ({ isSearchModeEnabled }: { isSearchModeEnabled: boolean }) => (isSearchModeEnabled ? 'left' : 'center'), -}); - -const rotationConfig = { - duration: 500, - easing: Easing.linear, -}; - -const timingConfig = { - duration: 300, -}; - -const ExchangeSearchShadowsFactory = (c: Colors) => [ - [0, 7, 21, c.shadow, 0.15], - [0, 3.5, 10.5, c.shadow, 0.04], -]; - -interface ExchangeSearchProps { - isDiscover?: boolean; - isFetching: boolean; - isSearching: boolean; - onChangeText: (arg: string) => void; - onFocus: ({ target }: any) => void; - searchQuery: string; - testID: string; - placeholderText?: string; - clearTextOnFocus: boolean; -} - -const ExchangeSearch: ForwardRefRenderFunction = ( - { - isDiscover, - isFetching, - isSearching, - onChangeText, - onFocus, - searchQuery, - testID, - placeholderText = lang.t('button.exchange_search_placeholder'), - clearTextOnFocus = true, - }, - ref -) => { - const inputRef = ref as MutableRefObject; - const handleClearInput = useCallback(() => { - if (isDiscover && searchQuery.length > 1) { - analytics.track('Search Query', { - category: 'discover', - length: searchQuery.length, - query: searchQuery, - }); - } - inputRef?.current?.clear(); - onChangeText?.(''); - }, [isDiscover, searchQuery, inputRef, onChangeText]); - - const ExchangeSearchWidthFocused = isDiscover ? ExchangeSearchWidth - DoneButtonWidth : ExchangeSearchWidth; - - const spinnerRotation = useSharedValue(0); - const spinnerScale = useSharedValue(0); - const { isSearchModeEnabled = true } = useContext(DiscoverSheetContext) ?? { - isSearchModeEnabled: true, - }; - - const spinnerTimeout = useRef(); - const { colors } = useTheme(); - const shadows = useMemo(() => ExchangeSearchShadowsFactory(colors), [colors]); - - useEffect(() => { - if ((isFetching || isSearching) && !isEmpty(searchQuery)) { - clearTimeout(spinnerTimeout.current as NodeJS.Timeout); - spinnerRotation.value = 0; - spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); - spinnerScale.value = withTiming(1, timingConfig); - } else { - spinnerScale.value = withTiming(0, timingConfig); - spinnerTimeout.current = setTimeout(() => (spinnerRotation.value = 0), timingConfig.duration); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFetching, isSearching, searchQuery]); - - const searchIconStyle = useAnimatedStyle(() => { - return { - opacity: 1 - spinnerScale.value, - transform: [{ scale: 1 - spinnerScale.value }], - }; - }); - - const spinnerStyle = useAnimatedStyle(() => { - return { - opacity: spinnerScale.value, - transform: [{ rotate: `${spinnerRotation.value}deg` }, { scale: spinnerScale.value }], - }; - }); - - return ( - - - - - {isSearchModeEnabled && !IS_TEST && ( - <> - - - 􀊫 - - - - - - - - )} - - - - - ); -}; - -export default React.forwardRef(ExchangeSearch); diff --git a/src/components/exchange/NetworkSwitcher.js b/src/components/exchange/NetworkSwitcher.js deleted file mode 100644 index 4e71d8be1ec..00000000000 --- a/src/components/exchange/NetworkSwitcher.js +++ /dev/null @@ -1,112 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import RadialGradient from 'react-native-radial-gradient'; -import Divider from '@/components/Divider'; -import ChainBadge from '@/components/coin-icon/ChainBadge'; -import { ContextMenuButton } from '@/components/context-menu'; -import { Column, Row } from '../layout'; -import { Text } from '../text'; -import { padding, position } from '@/styles'; -import { showActionSheetWithOptions } from '@/utils'; -import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { chainsLabel, chainsName, defaultChains, supportedSwapChainIds } from '@/chains'; -import { ChainId } from '@/chains/types'; - -const networkMenuItems = supportedSwapChainIds - .map(chainId => defaultChains[chainId]) - .map(chain => ({ - actionKey: `${chain.id}`, - actionTitle: chainsLabel[chain.id], - icon: { - iconType: 'ASSET', - iconValue: `${chainsName[chain.id]}Badge${chain.id === ChainId.mainnet ? '' : 'NoShadow'}`, - }, - })); - -const androidNetworkMenuItems = supportedSwapChainIds.map(chainId => defaultChains[chainId].id); - -const NetworkSwitcherv1 = ({ - colors, - hideDivider, - marginVertical = 12, - marginHorizontal = 19, - currentChainId, - setCurrentChainId, - testID, - prominent, -}) => { - const radialGradientProps = { - center: [0, 1], - colors: colors.gradients.lightGreyWhite, - pointerEvents: 'none', - style: { - ...position.coverAsObject, - overflow: 'hidden', - }, - }; - - const handleOnPressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }) => { - setCurrentChainId(actionKey); - }, - [setCurrentChainId] - ); - const onPressAndroid = useCallback(() => { - const networkActions = androidNetworkMenuItems; - showActionSheetWithOptions( - { - options: networkActions, - showSeparators: true, - }, - idx => { - if (idx !== undefined) { - setCurrentChainId(networkActions[idx]); - } - } - ); - }, [setCurrentChainId]); - - return ( - <> - - - - - {currentChainId !== 1 ? : } - - - - {lang.t('expanded_state.swap.network_switcher', { - network: chainsName[currentChainId], - })} - - - - - 􀆈 - - - - - {hideDivider ? null : } - - ); -}; - -export default NetworkSwitcherv1; diff --git a/src/components/exchange/NetworkSwitcherv2.tsx b/src/components/exchange/NetworkSwitcherv2.tsx deleted file mode 100644 index a293f31be76..00000000000 --- a/src/components/exchange/NetworkSwitcherv2.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { useRef, useMemo } from 'react'; -import { ScrollView } from 'react-native'; -import RadialGradient from 'react-native-radial-gradient'; -import { ButtonPressAnimation } from '../animations'; -import ChainBadge from '../coin-icon/ChainBadge'; -import { Bleed, Box, Columns, Inline, Text } from '@/design-system'; -import { position } from '@rainbow-me/styles'; -import { useTheme } from '@/theme'; -import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/chains/types'; -import { chainsLabel, defaultChains, supportedSwapChainIds } from '@/chains'; - -const NetworkSwitcherv2 = ({ - currentChainId, - setCurrentChainId, - testID, -}: { - currentChainId: number; - setCurrentChainId(chainId: number): void; - testID: string; -}) => { - const { colors } = useTheme(); - const scrollViewRef = useRef(null); - const networkMenuItems = useMemo(() => { - return Object.values(defaultChains) - .filter(chain => supportedSwapChainIds.includes(chain.id)) - .map(chain => ({ - chainId: chain.id, - title: chainsLabel[chain.id], - })); - }, []); - - const radialGradientProps = (chainId: ChainId) => { - return { - center: [0, 1], - colors: [colors.alpha(colors.networkColors[chainId], 0.1), colors.alpha(colors.networkColors[chainId], 0.02)], - pointerEvents: 'none', - style: { - ...position.coverAsObject, - overflow: 'hidden', - }, - }; - }; - - return ( - <> - - - - {networkMenuItems.map(({ chainId, title }) => { - const isSelected = currentChainId === chainId; - return ( - setCurrentChainId(chainId)} - padding="8px" - testID={`${testID}-${chainId}`} - > - {isSelected && ( - - )} - - {chainId === ChainId.mainnet ? ( - - ) : ( - - )} - - - {title} - - - - - ); - })} - - - - - ); -}; - -export default NetworkSwitcherv2; diff --git a/src/components/exchange/PriceImpactWarning.tsx b/src/components/exchange/PriceImpactWarning.tsx deleted file mode 100644 index 06d12345758..00000000000 --- a/src/components/exchange/PriceImpactWarning.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import { StyleProp, ViewProps, ViewStyle } from 'react-native'; -import Animated from 'react-native-reanimated'; -import { ButtonPressAnimation } from '../animations'; -import { Box, ColorModeProvider, Inline, Text } from '@/design-system'; -import { position } from '@/styles'; -import { NO_PRICE_DATA_PERCENTAGE } from '@/hooks/usePriceImpactDetails'; - -interface PriceImpactWarningProps extends ViewProps { - onPress: () => void; - isHighPriceImpact: boolean; - priceImpactColor?: string; - priceImpactNativeAmount?: string | null; - priceImpactPercentDisplay?: string | null; - outputCurrencySymbol?: string | null; - style?: StyleProp; -} - -export default function PriceImpactWarning({ - onPress, - isHighPriceImpact, - priceImpactColor = 'primary', - priceImpactNativeAmount, - priceImpactPercentDisplay, - outputCurrencySymbol, - style, - ...props -}: PriceImpactWarningProps) { - const headingValue = priceImpactNativeAmount ?? priceImpactPercentDisplay; - const hasPriceData = priceImpactPercentDisplay !== NO_PRICE_DATA_PERCENTAGE; - const impactMsg = !hasPriceData - ? `${outputCurrencySymbol} ${lang.t('exchange.price_impact.no_data')}` - : lang.t('exchange.price_impact.small_market'); - return ( - - - {!isHighPriceImpact && headingValue && ( - - - - {`􀇿 `} - - {impactMsg} - - {hasPriceData && ( - {` • ${lang.t('exchange.price_impact.losing_prefix')} `} - )} - {hasPriceData && ( - - {headingValue} - - )} - - - - )} - - - ); -} diff --git a/src/components/exchange/index.ts b/src/components/exchange/index.ts deleted file mode 100644 index 851865261a9..00000000000 --- a/src/components/exchange/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export { default as ConfirmExchangeButton } from './ConfirmExchangeButton'; -export { default as CurrencySelectionList } from './CurrencySelectionList'; -export { default as CurrencySelectModalHeader } from './CurrencySelectModalHeader'; -export { default as ExchangeAssetList } from './ExchangeAssetList'; -export { default as ExchangeDetailsButton } from './ExchangeDetailsButton'; -export { default as ExchangeDetailsRow } from './ExchangeDetailsRow'; -export { default as ExchangeFloatingPanels } from './ExchangeFloatingPanels'; -export { default as ExchangeHeader } from './ExchangeHeader'; -export { default as ExchangeInput } from './ExchangeInput'; -export { default as ExchangeInputField } from './ExchangeInputField'; -export { default as ExchangeNativeField } from './ExchangeNativeField'; -export { default as ExchangeNotch } from './ExchangeNotch'; -export { default as ExchangeOutputField } from './ExchangeOutputField'; -export { default as ExchangeSearch } from './ExchangeSearch'; -export { default as PriceImpactWarning } from './PriceImpactWarning'; diff --git a/src/components/exchange/exchangeAssetRowContextMenuProps.ts b/src/components/exchangeAssetRowContextMenuProps.ts similarity index 98% rename from src/components/exchange/exchangeAssetRowContextMenuProps.ts rename to src/components/exchangeAssetRowContextMenuProps.ts index 63c776cebff..e16a28ec471 100644 --- a/src/components/exchange/exchangeAssetRowContextMenuProps.ts +++ b/src/components/exchangeAssetRowContextMenuProps.ts @@ -1,7 +1,7 @@ import lang from 'i18n-js'; import { startCase } from 'lodash'; import { NativeSyntheticEvent } from 'react-native'; -import { setClipboard } from '../../hooks/useClipboard'; +import { setClipboard } from '@/hooks/useClipboard'; import { abbreviations, ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils'; import { ChainId } from '@/chains/types'; import { chainsIdByName } from '@/chains'; diff --git a/src/components/expanded-state/AvailableNetworksv2.tsx b/src/components/expanded-state/AvailableNetworksv2.tsx index b9deb453b94..0f07a751d1a 100644 --- a/src/components/expanded-state/AvailableNetworksv2.tsx +++ b/src/components/expanded-state/AvailableNetworksv2.tsx @@ -8,23 +8,20 @@ import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { position } from '@/styles'; import { watchingAlert } from '@/utils'; -import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; -import { useSwapCurrencyHandlers, useWallets } from '@/hooks'; +import { useWallets } from '@/hooks'; import { RainbowToken } from '@/entities'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { implementation } from '@/entities/dispersion'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { SWAPS_V2, enableActionsOnReadOnlyWallet, useExperimentalFlag } from '@/config'; -import { useRemoteConfig } from '@/model/remoteConfig'; +import { enableActionsOnReadOnlyWallet } from '@/config'; import { userAssetsStore } from '@/state/assets/userAssets'; import { parseSearchAsset } from '@/__swaps__/utils/assets'; import { AddressOrEth, AssetType } from '@/__swaps__/types/assets'; import { swapsStore } from '@/state/swaps/swapsStore'; import { InteractionManager } from 'react-native'; import { ChainId } from '@/chains/types'; -import { getUniqueId } from '@/utils/ethereumUtils'; import { chainsLabel, chainsName, defaultChains, supportedSwapChainIds } from '@/chains'; const NOOP = () => null; @@ -42,8 +39,6 @@ const AvailableNetworksv2 = ({ }) => { const { colors } = useTheme(); const { goBack, navigate } = useNavigation(); - const { swaps_v2 } = useRemoteConfig(); - const swapsV2Enabled = useExperimentalFlag(SWAPS_V2); const { isReadOnlyWallet } = useWallets(); const radialGradientProps = { @@ -56,10 +51,6 @@ const AvailableNetworksv2 = ({ }, }; - const { updateInputCurrency } = useSwapCurrencyHandlers({ - shouldUpdate: true, - type: ExchangeModalTypes.swap, - }); const convertAssetAndNavigate = useCallback( (chainId: ChainId) => { if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { @@ -76,74 +67,52 @@ const AvailableNetworksv2 = ({ goBack(); - if (swapsV2Enabled || swaps_v2) { - const uniqueId = `${newAsset.address}_${asset.chainId}`; - const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); - - const parsedAsset = parseSearchAsset({ - assetWithPrice: { - ...newAsset, - uniqueId, - address: newAsset.address as AddressOrEth, - type: newAsset.type as AssetType, - chainId: asset.chainId, - chainName: chainsName[asset.chainId], - isNativeAsset: false, - native: {}, - }, - searchAsset: { - ...newAsset, - uniqueId, - chainId: asset.chainId, - chainName: chainsName[asset.chainId], - address: newAsset.address as AddressOrEth, - highLiquidity: newAsset.highLiquidity ?? false, - isRainbowCurated: newAsset.isRainbowCurated ?? false, - isVerified: newAsset.isVerified ?? false, - mainnetAddress: (newAsset.mainnet_address ?? '') as AddressOrEth, - networks: newAsset.networks ?? [], - type: newAsset.type as AssetType, - }, - userAsset, - }); - - const largestBalanceSameChainUserAsset = userAssetsStore - .getState() - .getUserAssets() - .find(userAsset => userAsset.chainId === asset.chainId && userAsset.address !== newAsset.address); - if (largestBalanceSameChainUserAsset) { - swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); - } else { - swapsStore.setState({ inputAsset: null }); - } - swapsStore.setState({ outputAsset: parsedAsset }); - - InteractionManager.runAfterInteractions(() => { - navigate(Routes.SWAP); - }); + const uniqueId = `${newAsset.address}_${asset.chainId}`; + const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); + + const parsedAsset = parseSearchAsset({ + assetWithPrice: { + ...newAsset, + uniqueId, + address: newAsset.address as AddressOrEth, + type: newAsset.type as AssetType, + chainId: asset.chainId, + chainName: chainsName[asset.chainId], + isNativeAsset: false, + native: {}, + }, + searchAsset: { + ...newAsset, + uniqueId, + chainId: asset.chainId, + chainName: chainsName[asset.chainId], + address: newAsset.address as AddressOrEth, + highLiquidity: newAsset.highLiquidity ?? false, + isRainbowCurated: newAsset.isRainbowCurated ?? false, + isVerified: newAsset.isVerified ?? false, + mainnetAddress: (newAsset.mainnet_address ?? '') as AddressOrEth, + networks: newAsset.networks ?? [], + type: newAsset.type as AssetType, + }, + userAsset, + }); - return; + const largestBalanceSameChainUserAsset = userAssetsStore + .getState() + .getUserAssets() + .find(userAsset => userAsset.chainId === asset.chainId && userAsset.address !== newAsset.address); + if (largestBalanceSameChainUserAsset) { + swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); + } else { + swapsStore.setState({ inputAsset: null }); } + swapsStore.setState({ outputAsset: parsedAsset }); - newAsset.uniqueId = getUniqueId(asset.address, chainId); - newAsset.type = chainsName[chainId]; - - navigate(Routes.EXCHANGE_MODAL, { - params: { - fromDiscover: true, - ignoreInitialTypeCheck: true, - defaultOutputAsset: newAsset, - type: CurrencySelectionTypes.input, - showCoinIcon: true, - title: lang.t('swap.modal_types.get_symbol_with', { - symbol: newAsset?.symbol, - }), - onSelectCurrency: updateInputCurrency, - }, - screen: Routes.CURRENCY_SELECT_SCREEN, + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); }); }, - [asset, goBack, isReadOnlyWallet, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] + [asset, goBack, isReadOnlyWallet, navigate, networks] ); const handlePressContextMenu = useCallback( diff --git a/src/components/expanded-state/CustomGasState.js b/src/components/expanded-state/CustomGasState.js index 425befacda5..90fdb475d36 100644 --- a/src/components/expanded-state/CustomGasState.js +++ b/src/components/expanded-state/CustomGasState.js @@ -2,12 +2,11 @@ import { useIsFocused, useRoute } from '@react-navigation/native'; import React, { useEffect, useState } from 'react'; import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; import Divider from '@/components/Divider'; -import { ExchangeHeader } from '../exchange'; +import { ExchangeHeader } from '@/components/ExchangeHeader'; import { FloatingPanel } from '../floating-panels'; -import { GasSpeedButton } from '../gas'; -import { Column } from '../layout'; -import { SlackSheet } from '../sheet'; -import { FeesPanel, FeesPanelTabs } from './custom-gas'; +import { GasSpeedButton } from '@/components/gas'; +import { Column } from '@/components/layout'; +import { SlackSheet } from '@/components/sheet'; import { getTrendKey } from '@/helpers/gas'; import { useColorForAsset, useDimensions, useGas, useKeyboardHeight } from '@/hooks'; import { useNavigation } from '@/navigation'; @@ -15,8 +14,8 @@ import styled from '@/styled-thing'; import { margin } from '@/styles'; import { deviceUtils } from '@/utils'; import { IS_ANDROID } from '@/env'; -import { useSelector } from 'react-redux'; -import { getCrosschainSwapServiceTime } from '@/handlers/swap'; +import FeesPanel from '@/components/FeesPanel'; +import FeesPanelTabs from '@/components/FeesPanelTabs'; const FOOTER_HEIGHT = 120; const CONTENT_HEIGHT = 310; @@ -42,7 +41,6 @@ export default function CustomGasState({ asset }) { const colorForAsset = useColorForAsset(asset || {}, fallbackColor, false, true); const { selectedGasFee, currentBlockParams, chainId } = useGas(); const [canGoBack, setCanGoBack] = useState(true); - const { tradeDetails } = useSelector(state => state.swap); const validateGasParams = useRef(null); useAndroidDisableGesturesOnFocus(); @@ -99,7 +97,6 @@ export default function CustomGasState({ asset }) { theme="dark" validateGasParams={validateGasParams} marginTop={19} - crossChainServiceTime={getCrosschainSwapServiceTime(tradeDetails)} /> diff --git a/src/components/expanded-state/SwapDetailsState.js b/src/components/expanded-state/SwapDetailsState.js deleted file mode 100644 index 19af013c189..00000000000 --- a/src/components/expanded-state/SwapDetailsState.js +++ /dev/null @@ -1,187 +0,0 @@ -import useSwapDetailsButtonPosition from './useSwapDetailsButtonPosition'; -import { useIsFocused, useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import React, { useCallback, useEffect, useState } from 'react'; -import Animated, { useSharedValue } from 'react-native-reanimated'; - -import { useSelector } from 'react-redux'; -import { ConfirmExchangeButton } from '../exchange'; -import { GasSpeedButton } from '../gas'; -import { Column } from '../layout'; -import { SheetHandleFixedToTopHeight, SheetKeyboardAnimation, SheetTitle, SlackSheet } from '../sheet'; -import { CopyToast, ToastPositionContainer } from '../toasts'; -import { SwapDetailsContent, SwapDetailsMasthead, SwapDetailsMastheadHeight, SwapDetailsSlippageMessage } from './swap-details'; -import { useHeight, usePrevious, usePriceImpactDetails, useSwapCurrencies } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import styled from '@/styled-thing'; -import { padding, position } from '@/styles'; -import { abbreviations, ethereumUtils } from '@/utils'; -import { getCrosschainSwapServiceTime } from '@/handlers/swap'; -import { SwapPriceImpactType } from '@/hooks/usePriceImpactDetails'; -import { chainsIdByName } from '@/chains'; - -const AnimatedContainer = styled(Animated.View)({ - ...position.sizeAsObject('100%'), -}); - -const Footer = styled(Column).attrs({ - grow: 1, - shrink: 0, -})({ - ...padding.object(6, 0, 0), -}); - -const Header = styled(Column).attrs({ - justify: 'start', -})({ - left: 0, - position: 'absolute', - right: 0, - top: -2, - width: '100%', -}); - -const FOOTER_MIN_HEIGHT = 143; -const FOOTER_CONTENT_MIN_HEIGHT = 160; - -function useAndroidDisableGesturesOnFocus() { - const { params } = useRoute(); - const isFocused = useIsFocused(); - useEffect(() => { - android && params?.toggleGestureEnabled?.(!isFocused); - }, [isFocused, params]); -} - -function useSwapDetailsClipboardState() { - const [copiedText, setCopiedText] = useState(undefined); - const [copyCount, setCopyCount] = useState(0); - const onCopySwapDetailsText = useCallback(text => { - setCopiedText(abbreviations.formatAddressForDisplay(text)); - setCopyCount(count => count + 1); - }, []); - return { - copiedText, - copyCount, - onCopySwapDetailsText, - }; -} - -export default function SwapDetailsState({ confirmButtonProps, restoreFocusOnSwapModal }) { - const { setParams } = useNavigation(); - const isFocused = useIsFocused(); - const prevIsFocused = usePrevious(isFocused); - const { params: { longFormHeight, currentNetwork, flashbotTransaction, isRefuelTx, onClose } = {} } = useRoute(); - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - const chainId = chainsIdByName[currentNetwork]; - - const { - derivedValues: { inputAmount, outputAmount }, - displayValues: { inputAmountDisplay, outputAmountDisplay }, - tradeDetails, - } = useSelector(state => state.swap); - - const { priceImpact, inputNativeAmount, outputNativeAmount } = usePriceImpactDetails( - inputCurrency, - outputCurrency, - tradeDetails, - currentNetwork - ); - - const { copiedText, copyCount, onCopySwapDetailsText } = useSwapDetailsClipboardState(); - - const [footerHeight, setFooterHeight] = useHeight(FOOTER_MIN_HEIGHT); - const [slippageMessageHeight, setSlippageMessageHeight] = useHeight(); - const [contentHeight, setContentHeight] = useHeight(FOOTER_CONTENT_MIN_HEIGHT); - - const { onHeightChange, wrapperStyle, onPressMore, onWrapperLayout } = useSwapDetailsButtonPosition({ contentHeight }); - - const onContentHeightChange = useCallback( - event => { - onHeightChange(event); - setContentHeight(event); - }, - [onHeightChange, setContentHeight] - ); - - useEffect(() => { - if (!isFocused && prevIsFocused) { - return restoreFocusOnSwapModal(); - } - }, [isFocused, prevIsFocused, restoreFocusOnSwapModal]); - useAndroidDisableGesturesOnFocus(); - - const sheetHeightWithoutKeyboard = - SheetHandleFixedToTopHeight + SwapDetailsMastheadHeight + contentHeight + slippageMessageHeight + footerHeight; - - const contentScroll = useSharedValue(0); - - useEffect(() => { - setParams({ - longFormHeight: sheetHeightWithoutKeyboard, - transitionDuration: 0.7, - }); - }, [contentScroll, sheetHeightWithoutKeyboard, setParams]); - - useEffect(() => { - return () => { - onClose?.(); - }; - }, [onClose]); - - return ( - - -
- {lang.t('expanded_state.swap_details.review')} -
- - - - -
- - -
-
- - - -
-
- ); -} diff --git a/src/components/expanded-state/asset/ChartExpandedState.js b/src/components/expanded-state/asset/ChartExpandedState.js index a2a1de36965..1626e0363cf 100644 --- a/src/components/expanded-state/asset/ChartExpandedState.js +++ b/src/components/expanded-state/asset/ChartExpandedState.js @@ -16,7 +16,7 @@ import ExpandedStateSection from '../ExpandedStateSection'; import SocialLinks from './SocialLinks'; import { ChartPathProvider } from '@/react-native-animated-charts/src'; import { isL2Chain, isTestnetChain } from '@/handlers/web3'; -import AssetInputTypes from '@/helpers/assetInputTypes'; +import { SwapAssetType } from '@/__swaps__/types/swap'; import { useAccountSettings, useAdditionalAssetData, @@ -292,7 +292,7 @@ export default function ChartExpandedState({ asset }) { {!needsEth ? ( {hasBalance && !isTestnet && swapEnabled && ( - + )} {hasBalance ? ( isTransferable ? ( @@ -303,7 +303,7 @@ export default function ChartExpandedState({ asset }) { asset={assetWithPrice} color={color} fromDiscover={fromDiscover} - inputType={AssetInputTypes.out} + inputType={SwapAssetType.outputAsset} label={`􀖅 ${lang.t('expanded_state.asset.get_asset', { assetSymbol: asset?.symbol, })}`} @@ -315,7 +315,7 @@ export default function ChartExpandedState({ asset }) { ) : addCashEnabled ? ( - + ) : null} {!data?.networks && isL2 && ( diff --git a/src/components/expanded-state/custom-gas/index.js b/src/components/expanded-state/custom-gas/index.js deleted file mode 100644 index d0346c63712..00000000000 --- a/src/components/expanded-state/custom-gas/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as FeesGweiInput } from './FeesGweiInput'; -export { default as FeesPanelTabs } from './FeesPanelTabs'; -export { default as FeesPanel } from './FeesPanel'; diff --git a/src/components/expanded-state/index.js b/src/components/expanded-state/index.ts similarity index 74% rename from src/components/expanded-state/index.js rename to src/components/expanded-state/index.ts index 2372a750190..82ec12ad6ec 100644 --- a/src/components/expanded-state/index.js +++ b/src/components/expanded-state/index.ts @@ -1,8 +1,6 @@ export { default as ChartExpandedState } from './asset/ChartExpandedState'; -export { default as ContactProfileState } from './ContactProfileState'; export { default as CustomGasState } from './CustomGasState'; -export { default as SwapDetailsState } from './SwapDetailsState'; -export { default as SwapSettingsState } from './swap-settings/SwapSettingsState'; +export { default as ContactProfileState } from './ContactProfileState'; export { default as UniqueTokenExpandedState } from './UniqueTokenExpandedState'; export { default as WalletProfileState } from './WalletProfileState'; export { default as NewWalletGroupState } from './NewWalletGroupState'; diff --git a/src/components/expanded-state/swap-details/CurrencyTile.js b/src/components/expanded-state/swap-details/CurrencyTile.js deleted file mode 100644 index 62072cc8930..00000000000 --- a/src/components/expanded-state/swap-details/CurrencyTile.js +++ /dev/null @@ -1,118 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import { Centered } from '../../layout'; -import { Text, TruncatedText } from '../../text'; -import { Box, Column, Columns, Row, Rows } from '@/design-system'; -import { useAccountSettings, useColorForAsset, useDimensions } from '@/hooks'; -import { SwapModalField } from '@/redux/swap'; -import styled from '@/styled-thing'; -import { position } from '@/styles'; -import { convertAmountToNativeDisplay } from '@/helpers/utilities'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { ethereumUtils } from '@/utils'; - -export const CurrencyTileHeight = android ? 153 : 143; - -const AmountText = styled(Text).attrs(({ theme: { colors } }) => ({ - color: colors.alpha(colors.blueGreyDark, 0.8), - letterSpacing: 'roundedTight', - size: 'smedium', - weight: 'semibold', -}))(); - -const Container = styled(Centered).attrs({ - direction: 'column', -})({ - borderRadius: 30, - flex: 1, - height: CurrencyTileHeight, - overflow: 'hidden', - zIndex: 0, - ...(android ? { paddingTop: 4 } : {}), -}); - -const Gradient = styled(Box).attrs(({ theme: { colors }, color }) => ({ - backgroundColor: colors.alpha(color, 0.08), -}))({ - ...position.coverAsObject, -}); - -const NativePriceText = styled(TruncatedText).attrs({ - letterSpacing: 'roundedTight', - size: 'lmedium', - weight: 'heavy', -})({ - maxWidth: ({ maxWidth }) => maxWidth, -}); - -const TruncatedAmountText = styled(AmountText)({ - flexGrow: 0, - flexShrink: 1, -}); - -export default function CurrencyTile({ - amount, - amountDisplay, - asset, - isHighPriceImpact, - priceImpactColor, - priceValue, - type = 'input', - ...props -}) { - const { width } = useDimensions(); - const inputAsExact = useSelector(state => state.swap.independentField !== SwapModalField.output); - const { nativeCurrency } = useAccountSettings(); - const colorForAsset = useColorForAsset(asset); - const theme = useTheme(); - const isOther = (inputAsExact && type === 'output') || (!inputAsExact && type === 'input'); - - const priceDisplay = priceValue ? convertAmountToNativeDisplay(priceValue, nativeCurrency) : '-'; - - return ( - - - - - - - - - - - - - - - {isOther && '~'} - {amountDisplay} - - - - {asset?.symbol} - - - - - - - {priceDisplay} - {isHighPriceImpact && {` 􀇿`}} - - - - - - - - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsContent.js b/src/components/expanded-state/swap-details/SwapDetailsContent.js deleted file mode 100644 index 56abec2e201..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsContent.js +++ /dev/null @@ -1,127 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import { useSelector, View } from 'react-redux'; -import { ButtonPressAnimation } from '../../animations'; -import SwapDetailsContractRow from './SwapDetailsContractRow'; -import SwapDetailsExchangeRow from './SwapDetailsExchangeRow'; -import SwapDetailsFeeRow from './SwapDetailsFeeRow'; -import SwapDetailsPriceRow from './SwapDetailsPriceRow'; -import SwapDetailsRefuelRow from './SwapDetailsRefuelRow'; -import SwapDetailsRow, { SwapDetailsValue } from './SwapDetailsRow'; -import { AccentColorProvider, Box, Rows, Separator } from '@/design-system'; -import { isNativeAsset } from '@/handlers/assets'; -import Routes from '@/navigation/routesNames'; -import { useAccountSettings, useColorForAsset, useSwapAdjustedAmounts, useSwapCurrencies } from '@/hooks'; -import { SwapModalField } from '@/redux/swap'; -import styled from '@/styled-thing'; -import { padding } from '@/styles'; - -import { useNavigation } from '@/navigation'; -import { SwapDetailsRewardRow } from './SwapDetailsRewardRow'; -import useExperimentalFlag, { OP_REWARDS } from '@rainbow-me/config/experimentalHooks'; -import { useRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/chains/types'; - -const Container = styled(Box).attrs({ - flex: 1, -})(({ hasWarning }) => padding.object(hasWarning ? 24 : 30, 19, 30)); - -export default function SwapDetailsContent({ isHighPriceImpact, isRefuelTx, onCopySwapDetailsText, tradeDetails, onPressMore, ...props }) { - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - const { amountReceivedSold, receivedSoldLabel } = useSwapAdjustedAmounts(tradeDetails); - const { navigate } = useNavigation(); - const { flashbotsEnabled } = useAccountSettings(); - const inputAsExact = useSelector(state => state.swap.independentField !== SwapModalField.output); - - const [detailsExpanded, setDetailsExpanded] = useState(false); - - const colorForAsset = useColorForAsset(outputCurrency, undefined, true, true); - const inputChainId = inputCurrency.chainId; - const outputChainId = outputCurrency.chainId; - const { op_rewards_enabled } = useRemoteConfig(); - const hasReward = (useExperimentalFlag(OP_REWARDS) || op_rewards_enabled) && !!tradeDetails.reward?.[0]; - - return ( - - - - - - {amountReceivedSold} {inputAsExact ? outputCurrency.symbol : inputCurrency.symbol} - - - - {(isRefuelTx || tradeDetails?.refuel) && } - - {tradeDetails?.protocols && ( - - )} - {tradeDetails.feePercentageBasisPoints !== 0 && ( - - )} - {hasReward && } - {flashbotsEnabled && inputChainId === ChainId.mainnet && ( - - navigate(Routes.EXPLAIN_SHEET, { - type: 'flashbots', - }) - } - label={`${lang.t('expanded_state.swap.flashbots_protect')} 􀅵`} - testID="swaps-details-flashbots-row" - > - {`${lang.t('expanded_state.swap.on')} 􀞜`} - - )} - {!detailsExpanded && ( - - { - setDetailsExpanded(!detailsExpanded); - onPressMore(); - }} - scaleTo={1.06} - style={{ - // enlarge tap target for details button - paddingVertical: 18, - }} - testID="swaps-details-show-details-button" - > - - {detailsExpanded ? '􀁮' : '􀁰'} - - - - )} - {detailsExpanded && ( - - - - {!isNativeAsset(inputCurrency?.address, inputChainId) && ( - - )} - {!isNativeAsset(outputCurrency?.address, outputChainId) && ( - - )} - - )} - - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsContractRow.js b/src/components/expanded-state/swap-details/SwapDetailsContractRow.js deleted file mode 100644 index 2f8d9a48795..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsContractRow.js +++ /dev/null @@ -1,178 +0,0 @@ -import lang from 'i18n-js'; -import { startCase } from 'lodash'; -import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react'; -import { ContextMenuButton } from 'react-native-ios-context-menu'; -import Animated, { interpolate, interpolateColor, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; -import { ButtonPressAnimation } from '../../animations'; -import { Text, TruncatedAddress } from '../../text'; -import SwapDetailsRow from './SwapDetailsRow'; -import { useClipboard, useColorForAsset } from '@/hooks'; -import styled from '@/styled-thing'; -import { fonts, fontWithWidth } from '@/styles'; -import { abbreviations, ethereumUtils, showActionSheetWithOptions } from '@/utils'; - -const SwapDetailsText = styled(Text).attrs({ - lineHeight: android ? 18 : 17, -})(); - -export const SwapDetailsValue = styled(SwapDetailsText).attrs(({ theme: { colors }, color = colors.alpha(colors.blueGreyDark, 0.8) }) => ({ - color, -}))(fontWithWidth(fonts.weight.bold)); - -const AnimatedTruncatedAddress = Animated.createAnimatedComponent(TruncatedAddress); -const AnimatedText = Animated.createAnimatedComponent(Text); - -const ContractActionsEnum = { - blockExplorer: 'blockExplorer', - copyAddress: 'copyAddress', -}; - -const ContractActions = { - [ContractActionsEnum.copyAddress]: { - actionKey: ContractActionsEnum.copyAddress, - actionTitle: lang.t('wallet.action.copy_contract_address'), - icon: { - iconType: 'SYSTEM', - iconValue: 'doc.on.doc', - }, - }, -}; - -const buildBlockExplorerAction = chainId => { - const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), - }); - return { - actionKey: ContractActionsEnum.blockExplorer, - actionTitle: blockExplorerText, - icon: { - iconType: 'SYSTEM', - iconValue: 'link', - }, - }; -}; - -function SwapDetailsContractRowContent({ asset, menuVisible, scaleTo = 1.06, ...props }) { - const { colors } = useTheme(); - const colorForAsset = useColorForAsset(asset); - const animation = useSharedValue(menuVisible ? 1 : 0); - const startingColor = useMemo(() => colors.alpha(colors.blueGreyDark, 0.8), [colors]); - - useLayoutEffect(() => { - animation.value = withTiming(menuVisible ? 1 : 0, { duration: 150 }); - }, [menuVisible, animation]); - - const colorStyle = useAnimatedStyle(() => { - const color = interpolateColor(animation.value, [0, 1], [startingColor, colorForAsset]); - return { - color, - }; - }, [animation, colorForAsset]); - - const scaleStyle = useAnimatedStyle(() => { - const scale = interpolate(animation.value, [0, 1], [1, scaleTo]); - - return { - transform: [{ scale }], - }; - }); - - return ( - - - - - {` 􀍡`} - - - - ); -} - -export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, ...props }) { - const { setClipboard } = useClipboard(); - const handleCopyContractAddress = useCallback( - address => { - setClipboard(address); - onCopySwapDetailsText(address); - }, - [onCopySwapDetailsText, setClipboard] - ); - const [menuVisible, setMenuVisible] = useState(false); - - const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(asset?.chainId); - return { - menuItems: [ - blockExplorerAction, - { - ...ContractActions[ContractActionsEnum.copyAddress], - discoverabilityTitle: abbreviations.formatAddressForDisplay(asset?.address), - }, - ], - menuTitle: `${asset?.name} (${asset?.symbol})`, - }; - }, [asset?.chainId, asset?.address, asset?.name, asset?.symbol]); - - const handlePressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }) => { - if (actionKey === ContractActionsEnum.copyAddress) { - handleCopyContractAddress(asset?.address); - } else if (actionKey === ContractActionsEnum.blockExplorer) { - ethereumUtils.openTokenEtherscanURL({ address: asset?.address, chainId: asset?.chainId }); - } - }, - [asset, handleCopyContractAddress] - ); - - const onPressAndroid = useCallback(() => { - const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), - }); - const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; - showActionSheetWithOptions( - { - cancelButtonIndex: 2, - options: androidContractActions, - showSeparators: true, - title: `${asset?.name} (${asset?.symbol})`, - }, - idx => { - if (idx === 0) { - handleCopyContractAddress(asset?.address); - } - if (idx === 1) { - ethereumUtils.openTokenEtherscanURL({ address: asset?.address, chainId: asset?.chainId }); - } - } - ); - }, [asset, handleCopyContractAddress]); - - const onShowMenu = () => setMenuVisible(true); - const onHideMenu = () => setMenuVisible(false); - - return ( - - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js deleted file mode 100644 index 7e0cb82c02c..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js +++ /dev/null @@ -1,265 +0,0 @@ -import lang from 'i18n-js'; -import { capitalize } from 'lodash'; -import React, { Fragment, useMemo } from 'react'; -import { convertAmountToPercentageDisplay } from '../../../helpers/utilities'; -import Pill from '../../Pill'; -import { ButtonPressAnimation } from '../../animations'; -import { SwapDetailsLabel, SwapDetailsValue } from './SwapDetailsRow'; -import { Bleed, Box, Column, Columns, Cover, Inline, Stack, Text, useForegroundColor } from '@/design-system'; -import { usePrevious, useStepper } from '@/hooks'; -import { ImgixImage } from '@/components/images'; -import { getExchangeIconUrl, magicMemo } from '@/utils'; -import { SocketBridges } from '@/references/swap/bridges'; -import { defaultChains } from '@/chains'; - -const parseExchangeName = name => { - const networks = Object.values(defaultChains).map(network => network.name.toLowerCase()); - - const removeNetworks = name => - networks.some(network => name.toLowerCase().includes(network)) ? name.slice(name.indexOf('_') + 1, name.length) : name; - - const removeBridge = name => name.replace('-bridge', ''); - - return removeNetworks(removeBridge(name)); -}; - -const ExchangeIcon = magicMemo( - function ExchangeIcon({ index = 1, icon, protocol }) { - const { colors } = useTheme(); - const [error, setError] = useState(false); - const previousIcon = usePrevious(icon); - if (error && icon !== previousIcon) { - setError(false); - } - - return ( - - {icon && !error ? ( - setError(true)} - size={20} - source={{ uri: icon }} - style={{ - borderColor: colors.white, - borderRadius: 10, - borderWidth: 1.5, - height: 20, - width: 20, - zIndex: index, - }} - /> - ) : ( - - - - - - {typeof protocol === 'string' ? protocol?.substring(0, 1)?.toUpperCase() : 'U'} - - - - - - )} - - ); - }, - ['icon', 'protocol'] -); - -const ExchangeIconStack = magicMemo( - ({ protocols }) => { - return ( - - {protocols?.icons?.map((icon, index) => { - return ( - - - - ); - })} - - ); - }, - ['protocols'] -); - -const CrossChainIconStack = magicMemo( - ({ protocols }) => { - return ( - - {protocols?.icons?.map((icon, index) => { - return ( - - - - {index < protocols?.icons.length - 1 && ( - - - 􀆊 - - - )} - - ); - })} - - ); - }, - ['protocols'] -); - -export default function SwapDetailsExchangeRow({ routes, protocols, testID }) { - const bridges = routes?.[0]?.usedBridgeNames; - - const steps = useMemo(() => { - const sortedProtocols = protocols?.sort((a, b) => b.part - a.part); - const defaultCase = { - icons: sortedProtocols.map(({ name }) => getExchangeIconUrl(parseExchangeName(name))), - label: lang.t(bridges ? 'expanded_state.swap_details.number_of_steps' : 'expanded_state.swap_details.number_of_exchanges', { - number: sortedProtocols?.length, - }), - names: sortedProtocols.map(({ name }) => name), - }; - if (sortedProtocols.length === 1) { - const protocol = sortedProtocols[0]; - const protocolName = parseExchangeName(protocol.name); - const isBridge = bridges?.includes(protocol.name); - - return [ - { - icons: [getExchangeIconUrl(protocolName)], - label: capitalize(protocolName.replace('_', ' ')), - name: SocketBridges[protocol.name] ?? protocolName.slice('_'), - action: isBridge ? lang.t('expanded_state.swap.bridge') : lang.t('expanded_state.swap.swap'), - part: convertAmountToPercentageDisplay(protocol.part), - }, - ]; - } - const mappedExchanges = sortedProtocols.map(protocol => { - const protocolName = parseExchangeName(protocol.name); - const part = convertAmountToPercentageDisplay(protocol.part, 0, 3, true); - const isBridge = bridges?.includes(protocol.name); - - return { - icons: [getExchangeIconUrl(protocolName)], - label: capitalize(protocolName.replace('_', ' ')), - name: SocketBridges[protocol.name] ?? protocolName.slice('_'), - isBridge, - action: isBridge ? lang.t('expanded_state.swap.bridge') : lang.t('expanded_state.swap.swap'), - part, - }; - }); - return [defaultCase, ...mappedExchanges]; - }, [bridges, protocols]); - - const [step, nextStep] = useStepper(steps.length); - const defaultColor = useForegroundColor('secondary (Deprecated)'); - - if (protocols?.length > 1) { - return ( - - - - - {lang.t('expanded_state.swap.swapping_via')} - - - - - {bridges ? : } - - - - - {steps[step].label} - - {(steps?.[step]?.part || steps?.[step]?.action) && ( - - - - {bridges ? steps[step].action : steps[step].part} - - - - )} - - - - ); - } else if (protocols?.length > 0) { - return ( - - - {lang.t('expanded_state.swap.swapping_via')} - - - - - - - - - - {steps[step].label} - - - - {`${protocols[0].part}%`} - - - - ); - } - return null; -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js b/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js deleted file mode 100644 index 675acd503c8..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js +++ /dev/null @@ -1,41 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import useRainbowFee from '../../../hooks/useRainbowFee'; -import SwapDetailsRow from './SwapDetailsRow'; -import { convertAmountToNativeDisplay, convertAmountToPercentageDisplayWithThreshold, isZero } from '@/helpers/utilities'; -import { useAccountSettings, useStepper } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; - -export default function SwapDetailsFeeRow({ tradeDetails, chainId, testID }) { - const { navigate } = useNavigation(); - const { nativeCurrency } = useAccountSettings(); - const { rainbowFeeNative, rainbowFeePercentage } = useRainbowFee({ - chainId, - tradeDetails, - }); - const rainbowFeeNativeDisplay = rainbowFeeNative && convertAmountToNativeDisplay(rainbowFeeNative, nativeCurrency); - const rainbowFeePercentageDisplay = isZero(rainbowFeePercentage) - ? '0.00%' - : convertAmountToPercentageDisplayWithThreshold(rainbowFeePercentage); - const steps = rainbowFeeNativeDisplay ? [rainbowFeeNativeDisplay, rainbowFeePercentageDisplay] : [rainbowFeePercentageDisplay]; - const [step, nextStep] = useStepper(steps.length); - - const handleLabelPress = useCallback(() => { - navigate(Routes.EXPLAIN_SHEET, { - feePercentage: rainbowFeePercentageDisplay, - type: 'rainbow_fee', - }); - }, [navigate, rainbowFeePercentageDisplay]); - - return ( - - {steps[step]} - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsMasthead.js b/src/components/expanded-state/swap-details/SwapDetailsMasthead.js deleted file mode 100644 index 7a00d7f8daa..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsMasthead.js +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import { RowWithMargins } from '../../layout'; -import CurrencyTile, { CurrencyTileHeight } from './CurrencyTile'; -import { Bleed, Box, Columns, Cover, Text } from '@/design-system'; -import { useSwapCurrencies } from '@/hooks'; - -const containerPaddingTop = 34; -export const SwapDetailsMastheadHeight = CurrencyTileHeight + containerPaddingTop; - -const DoubleChevron = () => ( - - - - 􀯻 - - - - 􀯻 - - - - -); - -export default function SwapDetailsMasthead({ - inputAmount, - inputAmountDisplay, - inputPriceValue, - isHighPriceImpact, - outputAmount, - outputAmountDisplay, - outputPriceValue, - priceImpactColor, - ...props -}) { - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - - return ( - - - - - - - - - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsPriceRow.js b/src/components/expanded-state/swap-details/SwapDetailsPriceRow.js deleted file mode 100644 index 303f2f1956a..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsPriceRow.js +++ /dev/null @@ -1,49 +0,0 @@ -import lang from 'i18n-js'; -import React, { useMemo } from 'react'; -import { ButtonPressAnimation } from '../../animations'; -import SwapDetailsRow, { SwapDetailsValue } from './SwapDetailsRow'; -import { convertRawAmountToDecimalFormat, divide, handleSignificantDecimals } from '@/helpers/utilities'; -import { useStepper, useSwapCurrencies } from '@/hooks'; - -export default function SwapDetailsPriceRow({ tradeDetails, ...props }) { - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - - const convertedSellAmount = convertRawAmountToDecimalFormat(tradeDetails?.sellAmount, inputCurrency.decimals); - - const convertedBuyAmount = convertRawAmountToDecimalFormat(tradeDetails?.buyAmount, outputCurrency.decimals); - - const outputExecutionRateRaw = divide(convertedSellAmount, convertedBuyAmount); - - const inputExecutionRateRaw = divide(convertedBuyAmount, convertedSellAmount); - - const inputExecutionRate = handleSignificantDecimals(inputExecutionRateRaw, 2); - - const outputExecutionRate = handleSignificantDecimals(outputExecutionRateRaw, 2); - - const steps = useMemo( - () => [ - lang.t('expanded_state.swap_details.output_exchange_rate', { - executionRate: outputExecutionRate, - inputSymbol: inputCurrency?.symbol, - outputSymbol: outputCurrency?.symbol, - }), - lang.t('expanded_state.swap_details.input_exchange_rate', { - executionRate: inputExecutionRate, - inputSymbol: inputCurrency?.symbol, - outputSymbol: outputCurrency?.symbol, - }), - ], - [inputCurrency, inputExecutionRate, outputCurrency, outputExecutionRate] - ); - - const [step, nextStep] = useStepper(steps.length); - - return ( - - - {steps[step]} - {` 􀅌`} - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js b/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js deleted file mode 100644 index f93113e0fb6..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js +++ /dev/null @@ -1,92 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import { SwapDetailsLabel, SwapDetailsValue } from './SwapDetailsRow'; -import { convertRawAmountToBalance } from '@/helpers/utilities'; -import { ethereumUtils } from '@/utils'; -import { Box, Column, Columns, Rows } from '@/design-system'; -import styled from '@/styled-thing'; -import { ImgixImage } from '@/components/images'; -import CaretImageSource from '@/assets/family-dropdown-arrow.png'; -import Spinner from '@/components/Spinner'; -import { useTheme } from '@/theme'; -import { useNativeAsset } from '@/utils/ethereumUtils'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; - -const CaretIcon = styled(ImgixImage).attrs(({ theme: { colors } }) => ({ - resizeMode: ImgixImage.resizeMode.contain, - source: CaretImageSource, - tintColor: colors.blueGreyDark, - size: 30, -}))({ - height: 11, - top: 0, - width: 7, -}); - -const ICON_ALIGN_MARGIN = '-8px'; - -export default function SwapDetailsRefuelRow({ tradeDetails, testID }) { - const { colors } = useTheme(); - const fromAsset = tradeDetails?.refuel?.fromAsset; - const toAsset = tradeDetails?.refuel?.toAsset; - const toAmount = convertRawAmountToBalance(tradeDetails?.refuel?.toAmount, { - decimals: 18, - }); - const toSymbol = tradeDetails?.refuel?.toAsset?.symbol; - - const fromNativeAsset = useNativeAsset({ chainId: fromAsset?.chainId }); - const toNativeAsset = useNativeAsset({ chainId: toAsset?.chainId }); - - return ( - - - - {lang.t('expanded_state.swap_details.refuel')} - - {tradeDetails.refuel ? ( - <> - - - - - - - - - - - - - - - - - - {`${toAmount.display}${toSymbol}`} - - - ) : ( - - - - - - )} - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx deleted file mode 100644 index 017565f83bc..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { Bleed, Box, Text } from '@/design-system'; -import { SwapDetailsLabel } from './SwapDetailsRow'; -import { ButtonPressAnimation } from '@/components/animations'; -import { Reward } from '@rainbow-me/swaps'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import { ChainBadge } from '@/components/coin-icon'; -import { useTheme } from '@/theme'; -import * as i18n from '@/languages'; -import { ChainId } from '@/chains/types'; -import { chainsNativeAsset } from '@/chains'; - -export function SwapDetailsRewardRow({ reward }: { reward: Reward }) { - const { navigate } = useNavigation(); - const { isDarkMode } = useTheme(); - - const roundedAmount = Math.round(reward.amount * 1000) / 1000; - const nativeAsset = chainsNativeAsset[ChainId.optimism]; - const accentColor = isDarkMode ? nativeAsset.colors.primary : nativeAsset.colors.fallback || nativeAsset.colors.primary; - - return ( - - navigate(Routes.OP_REWARDS_SHEET)}> - {i18n.t(i18n.l.expanded_state.swap_details.reward)} 􀅵 - - - - - - {roundedAmount || '<0.001'} {reward.token.symbol} - - - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsRow.js b/src/components/expanded-state/swap-details/SwapDetailsRow.js deleted file mode 100644 index 03734909643..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsRow.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { Fragment } from 'react'; -import { ButtonPressAnimation } from '../../animations'; -import { Row } from '../../layout'; -import { Text } from '@/design-system'; - -export const SwapDetailsLabel = ({ children, color = 'secondary60 (Deprecated)' }) => { - return ( - - {children} - - ); -}; - -export const SwapDetailsValue = ({ children, color = 'secondary80 (Deprecated)' }) => { - return ( - - {children} - - ); -}; - -export default function SwapDetailsRow({ children, label, labelColor, labelPress, valuePress, ...props }) { - const LabelWrapper = labelPress ? ButtonPressAnimation : Fragment; - const ValueWrapper = valuePress ? ButtonPressAnimation : Fragment; - - return ( - - - {label} - - - {children} - - - ); -} diff --git a/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js b/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js deleted file mode 100644 index 135dd07202a..00000000000 --- a/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js +++ /dev/null @@ -1,71 +0,0 @@ -import lang from 'i18n-js'; -import React from 'react'; -import Divider from '@/components/Divider'; -import { Centered, Column, ColumnWithMargins, Row } from '../../layout'; -import { Emoji, Text } from '../../text'; -import styled from '@/styled-thing'; -import { padding } from '@/styles'; -import { NO_PRICE_DATA_PERCENTAGE } from '@/hooks/usePriceImpactDetails'; - -const Container = styled(ColumnWithMargins).attrs({ - align: 'center', - margin: 8, -})(padding.object(30, 42, 24)); - -const Heading = styled(Text).attrs(({ weight = 'bold' }) => ({ - size: 'larger', - weight, -}))({}); - -const Message = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.5), - lineHeight: 22, - size: 'smedium', - weight: 'semibold', -}))({}); - -export default function SwapDetailsSlippageMessage({ - isHighPriceImpact, - priceImpactColor, - priceImpactNativeAmount, - priceImpactPercentDisplay, - outputCurrencySymbol, - ...props -}) { - const { colors } = useTheme(); - const headingValue = priceImpactNativeAmount ?? priceImpactPercentDisplay; - const hasPriceData = priceImpactPercentDisplay !== NO_PRICE_DATA_PERCENTAGE; - const impactMsg = `${outputCurrencySymbol} ${lang.t('exchange.price_impact.no_data')}`; - return isHighPriceImpact ? ( - - {hasPriceData ? ( - - - - {lang.t('expanded_state.swap.losing')}{' '} - - - {headingValue} - - 🥵 - - {lang.t('expanded_state.swap.slippage_message')} - - ) : ( - - - {`􀇿 `} - - {impactMsg} - - - {lang.t('exchange.price_impact.no_data_subtitle')} - - )} - - - - - ) : null; -} diff --git a/src/components/expanded-state/swap-details/index.js b/src/components/expanded-state/swap-details/index.js deleted file mode 100644 index 4815af0a17c..00000000000 --- a/src/components/expanded-state/swap-details/index.js +++ /dev/null @@ -1,8 +0,0 @@ -export { default as CurrencyTile, CurrencyTileHeight } from './CurrencyTile'; -export { default as SwapDetailsContent, SwapDetailsContentMinHeight } from './SwapDetailsContent'; -export { default as SwapDetailsContractRow } from './SwapDetailsContractRow'; -export { default as SwapDetailsMasthead, SwapDetailsMastheadHeight } from './SwapDetailsMasthead'; -export { default as SwapDetailsPriceRow } from './SwapDetailsPriceRow'; -export { default as SwapDetailsRow, SwapDetailsLabel, SwapDetailsRowHeight, SwapDetailsValue } from './SwapDetailsRow'; -export { default as SwapDetailsSlippageMessage } from './SwapDetailsSlippageMessage'; -export { default as SwapDetailsExchangeRow } from './SwapDetailsExchangeRow'; diff --git a/src/components/expanded-state/swap-settings/InputPill.js b/src/components/expanded-state/swap-settings/InputPill.js deleted file mode 100644 index 425010d1db7..00000000000 --- a/src/components/expanded-state/swap-settings/InputPill.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useCallback } from 'react'; -import LinearGradient from 'react-native-linear-gradient'; -import TextInputMask from 'react-native-text-input-mask'; -import { Row } from '../../../components/layout'; -import { Text } from '../../text'; -import styled from '@/styled-thing'; -import { buildTextStyles, margin, padding } from '@/styles'; - -const ANDROID_EXTRA_LINE_HEIGHT = 8; - -const PillGradient = styled(LinearGradient).attrs(({ theme: { colors } }) => ({ - colors: colors.gradients.lightGreyTransparent, - end: { x: 0.5, y: 1 }, - start: { x: 0, y: 0 }, -}))({ - borderRadius: 15, - height: 40, - ...(ios ? { height: 40 } : padding.object(10, 12)), - maxWidth: 130, - minWidth: 108, - ...(android ? { marginHorizontal: 5 } : {}), -}); - -const NumberInput = styled(TextInputMask).attrs(({ theme: { colors }, value }) => ({ - color: !value && colors.alpha(colors.blueGreyDark, 0.4), - interval: 1, - keyboardAppearance: 'dark', - keyboardType: 'decimal-pad', - letterSpacing: 'rounded', - size: 'lmedium', - textAlign: 'left', - timing: 'linear', - weight: 'heavy', - ...(ios && { - height: '100%', - left: 22, - paddingLeft: 28, - paddingRight: 72, - paddingVertical: 10.5, - }), -}))(props => ({ - ...buildTextStyles.object(props), - ...(android ? padding.object(0, 0, 0, 0) : {}), - ...margin.object(android ? -ANDROID_EXTRA_LINE_HEIGHT : 0, 0, android ? -ANDROID_EXTRA_LINE_HEIGHT : 0, 0), -})); - -const Label = styled(Text).attrs(() => ({ - align: 'center', - pointerEvents: 'none', - size: 'large', - weight: 'heavy', -}))({ - ...margin.object(android ? -ANDROID_EXTRA_LINE_HEIGHT : 0, 0, android ? -ANDROID_EXTRA_LINE_HEIGHT : 0, 0), - ...(ios ? { right: 40 } : {}), - top: android ? -0.5 : 8.5, -}); - -function InputPill({ color, label, onChange: onChangeCallback, onFocus, onBlur, testID, value }, ref) { - const { colors } = useTheme(); - - const onChangeText = useCallback( - text => { - text = text === '.' || text === ',' ? `0${text}` : text; - onChangeCallback(text); - }, - [onChangeCallback] - ); - - return ( - - - - - - - ); -} - -export default React.forwardRef(InputPill); diff --git a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx deleted file mode 100644 index e803f523096..00000000000 --- a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import lang from 'i18n-js'; -import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'; -import { Keyboard, TextInput } from 'react-native'; -import { getDefaultSlippageFromConfig } from '../../../screens/ExchangeModal'; -import { ButtonPressAnimation } from '../../animations'; -import { Icon } from '../../icons'; -import StepButtonInput from './StepButtonInput'; -import { AccentColorProvider, Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; -import { add, convertNumberToString, greaterThan } from '@/helpers/utilities'; -import { useMagicAutofocus, useSwapSettings } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import { colors } from '@/styles'; -import { ChainId } from '@/chains/types'; - -const convertBipsToPercent = (bips: number) => (bips / 100).toString(); -const convertPercentToBips = (percent: number) => (percent * 100).toString(); - -const SLIPPAGE_INCREMENT = 0.1; - -// eslint-disable-next-line react/display-name -export const MaxToleranceInput = forwardRef(({ colorForAsset, chainId }: { colorForAsset: string; chainId: ChainId }, ref) => { - const { slippageInBips, updateSwapSlippage } = useSwapSettings(); - const { navigate } = useNavigation(); - - const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); - - const slippageRef = useRef(null); - - const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); - - const { hasPriceImpact, priceImpactColor } = useMemo(() => { - const hasPriceImpact = Number(slippageValue) >= 3; - const priceImpactColor = hasPriceImpact ? colors.orange : null; - return { hasPriceImpact, priceImpactColor }; - }, [slippageValue]); - - useImperativeHandle(ref, () => ({ - blur: () => { - slippageRef?.current?.blur(); - }, - reset: () => { - const slippage = getDefaultSlippageFromConfig(chainId) as unknown as number; - onSlippageChange(convertBipsToPercent(slippage)); - }, - })); - - const updateSlippage = useCallback( - (increment: any) => { - const newSlippage = add(slippageValue, increment); - const newSlippageValue = convertNumberToString(newSlippage); - if (greaterThan(0, newSlippageValue)) return; - - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); - setSlippageValue(newSlippageValue); - }, - [slippageValue, updateSwapSlippage] - ); - - const addSlippage = useCallback(() => { - updateSlippage(SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const minusSlippage = useCallback(() => { - updateSlippage(-SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const onSlippageChange = useCallback( - (value: any) => { - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(value)); - setSlippageValue(value); - }, - [updateSwapSlippage, setSlippageValue] - ); - - const openSlippageExplainer = () => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - type: 'slippage', - }); - }; - - return ( - - - - - - - {`${lang.t('exchange.slippage_tolerance')} `} - {!hasPriceImpact && ( - - {' 􀅵'} - - )} - - {hasPriceImpact && ( - - - - )} - - - {hasPriceImpact && ( - - - - - {lang.t('exchange.high')} - - - {` · ${lang.t('exchange.price_impact.label')}`} - - - )} - - - - - - - ); -}); diff --git a/src/components/expanded-state/swap-settings/SourcePicker.js b/src/components/expanded-state/swap-settings/SourcePicker.js deleted file mode 100644 index a357d100991..00000000000 --- a/src/components/expanded-state/swap-settings/SourcePicker.js +++ /dev/null @@ -1,140 +0,0 @@ -import lang from 'i18n-js'; -import React, { useCallback } from 'react'; -import { Keyboard } from 'react-native'; -import RainbowExchange from '../../../assets/exchanges/both.png'; -import OneInchExchange from '../../../assets/exchanges/oneinch.png'; -import ZeroXExchange from '../../../assets/exchanges/zerox.png'; -import { ButtonPressAnimation } from '../../animations'; -import { ContextMenuButton } from '../../context-menu'; -import { Box, Column, Columns, Inline, Text } from '@/design-system'; -import { ImgixImage } from '@/components/images'; -import { useNavigation } from '@/navigation'; -import { Source } from '@/raps/references'; -import Routes from '@/navigation/routesNames'; - -import { showActionSheetWithOptions } from '@/utils'; - -const sourceMenuItems = () => { - return Object.values(Source).map(source => ({ - actionKey: source, - actionTitle: lang.t(`exchange.source.${source}`), - icon: { - iconType: 'ASSET', - iconValue: `${source}`, - }, - })); -}; - -const androidSourceMenuItems = () => { - return Object.values(Source).reduce((obj, key) => ((obj[key] = lang.t(`exchange.source.${key}`)), obj), {}); -}; - -export default function SourcePicker({ onSelect, currentSource }) { - const { navigate } = useNavigation(); - const imageSource = useMemo(() => { - let source = null; - switch (currentSource) { - case Source.Aggregator1inch: - source = OneInchExchange; - break; - case Source.Aggregator0x: - source = ZeroXExchange; - break; - default: - source = RainbowExchange; - break; - } - - return source; - }, [currentSource]); - - const handleOnPressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }) => { - onSelect(actionKey); - }, - [onSelect] - ); - const onPressAndroid = useCallback(() => { - const menuOptions = Object.values(androidSourceMenuItems()); - showActionSheetWithOptions( - { - options: menuOptions, - showSeparators: true, - }, - idx => { - if (idx !== undefined) { - const menuOptionsKeys = Object.keys(androidSourceMenuItems()); - onSelect(menuOptionsKeys[idx]); - } - } - ); - }, [onSelect]); - - const openRoutesExplainer = () => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - type: 'routeSwaps', - }); - }; - - return ( - - - - - {lang.t('exchange.source_picker')} - - {' 􀅵'} - - - - - - - - > - - - - {`${lang.t(`exchange.source.${currentSource}`)} 􀆈`} - - - - - - - ); -} diff --git a/src/components/expanded-state/swap-settings/StepButtonInput.js b/src/components/expanded-state/swap-settings/StepButtonInput.js deleted file mode 100644 index 8e5c200a075..00000000000 --- a/src/components/expanded-state/swap-settings/StepButtonInput.js +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import { ButtonPressAnimation } from '../../animations'; -import { Row } from '../../layout'; -import { Text } from '../../text'; -import InputPill from './InputPill'; -import { delay } from '@/helpers/utilities'; -import { usePrevious } from '@/hooks'; -import styled from '@/styled-thing'; - -const PLUS_ACTION_TYPE = 'plus'; -const MINUS_ACTION_TYPE = 'minus'; -const LONG_PRESS_DELAY_THRESHOLD = 69; -const MIN_LONG_PRESS_DELAY_THRESHOLD = 200; - -const Wrapper = styled(Row)({}); - -const StepButtonWrapper = styled(ButtonPressAnimation).attrs(() => ({ - paddingHorizontal: 7, - scaleTo: 0.75, -}))({}); - -const StepButtonText = styled(Text).attrs(({ theme: { colors }, color }) => ({ - color: color || colors.appleBlue, - lineHeight: 40, - size: 'lmedium', - weight: 'heavy', -}))({}); - -const StepButton = ({ type, onLongPress, onLongPressEnded, onPress, shouldLongPressHoldPress, buttonColor }) => { - return ( - - {type === 'plus' ? '􀁍' : '􀁏'} - - ); -}; - -export default function StepButtonInput({ - value, - inputLabel, - plusAction, - minusAction, - onBlur, - onChange, - onFocus, - buttonColor, - testID, - inputRef, -}) { - const longPressHandle = useRef(null); - const [trigger, setTrigger] = useState(false); - const [actionType, setActionType] = useState(null); - const prevTrigger = usePrevious(trigger); - - const onMinusPress = useCallback(() => { - longPressHandle.current = false; - minusAction(); - }, [minusAction]); - - const onPlusPress = useCallback(() => { - longPressHandle.current = false; - plusAction(); - }, [plusAction]); - - const onLongPressEnded = useCallback(() => { - longPressHandle.current = false; - setActionType(null); - }, [longPressHandle]); - - const onLongPressLoop = useCallback(async () => { - setTrigger(true); - setTrigger(false); - await delay(LONG_PRESS_DELAY_THRESHOLD); - longPressHandle.current && onLongPressLoop(); - }, []); - - const onLongPress = useCallback(async () => { - longPressHandle.current = true; - onLongPressLoop(); - }, [onLongPressLoop]); - - const onPlusLongPress = useCallback(() => { - setActionType(PLUS_ACTION_TYPE); - onLongPress(); - }, [onLongPress]); - - const onMinusLongPress = useCallback(() => { - setActionType(MINUS_ACTION_TYPE); - onLongPress(); - }, [onLongPress]); - - useEffect(() => { - if (!prevTrigger && trigger) { - if (actionType === PLUS_ACTION_TYPE) { - plusAction(); - if (!android) { - ReactNativeHapticFeedback.trigger('selection'); - } - } else if (actionType === MINUS_ACTION_TYPE) { - minusAction(); - if (!android) { - ReactNativeHapticFeedback.trigger('selection'); - } - } - } - }, [trigger, prevTrigger, actionType, plusAction, minusAction]); - - return ( - - - - - - ); -} diff --git a/src/components/expanded-state/swap-settings/SwapSettingsState.js b/src/components/expanded-state/swap-settings/SwapSettingsState.js deleted file mode 100644 index 7432ebece10..00000000000 --- a/src/components/expanded-state/swap-settings/SwapSettingsState.js +++ /dev/null @@ -1,201 +0,0 @@ -import { useIsFocused, useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import React, { useCallback, useEffect, useRef } from 'react'; -import { Keyboard } from 'react-native'; -import { Switch } from 'react-native-gesture-handler'; -import { useDispatch } from 'react-redux'; -import { ButtonPressAnimation } from '../../animations'; -import { ExchangeHeader } from '../../exchange'; -import { FloatingPanel } from '../../floating-panels'; -import { SlackSheet } from '../../sheet'; -import { MaxToleranceInput } from './MaxToleranceInput'; -import SourcePicker from './SourcePicker'; - -import { Box, ColorModeProvider, Column, Columns, Inset, Stack, Text } from '@/design-system'; - -import { useAccountSettings, useColorForAsset, useKeyboardHeight, useSwapSettings } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import { Source } from '@/raps/references'; -import Routes from '@/navigation/routesNames'; -import { deviceUtils } from '@/utils'; -import { IS_ANDROID } from '@/env'; -const MAX_TEXT_WIDTH = 210; - -function useAndroidDisableGesturesOnFocus() { - const { params } = useRoute(); - const isFocused = useIsFocused(); - useEffect(() => { - android && params?.toggleGestureEnabled?.(!isFocused); - }, [isFocused, params]); -} - -export default function SwapSettingsState({ asset }) { - const { flashbotsEnabled, settingsChangeFlashbotsEnabled } = useAccountSettings(); - const { - params: { swapSupportsFlashbots = false, chainId }, - } = useRoute(); - const { colors } = useTheme(); - const { setParams, goBack } = useNavigation(); - const dispatch = useDispatch(); - const keyboardHeight = useKeyboardHeight(); - const slippageRef = useRef(null); - const { updateSwapSource, source } = useSwapSettings(); - const isFocused = useIsFocused(); - const { navigate } = useNavigation(); - const keyboardShowListener = useRef(null); - const keyboardHideListener = useRef(null); - useAndroidDisableGesturesOnFocus(); - - const handleKeyboardDidHide = useCallback(() => { - if (isFocused) { - goBack(); - } - }, [goBack, isFocused]); - const handleKeyboardDidShow = useCallback(() => { - if (!isFocused) { - Keyboard.dismiss(); - } - }, [isFocused]); - const toggleFlashbotsEnabled = useCallback(async () => { - await dispatch(settingsChangeFlashbotsEnabled(!flashbotsEnabled)); - }, [dispatch, flashbotsEnabled, settingsChangeFlashbotsEnabled]); - - useEffect(() => { - if (IS_ANDROID) { - keyboardShowListener.current = Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow); - keyboardHideListener.current = Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide); - } - return () => { - keyboardShowListener.current?.remove(); - keyboardHideListener.current?.remove(); - }; - }, [handleKeyboardDidHide, handleKeyboardDidShow]); - - const colorForAsset = useColorForAsset(asset || {}, null, false, true); - - const [currentSource, setCurrentSource] = useState(source); - const updateSource = useCallback( - newSource => { - setCurrentSource(newSource); - updateSwapSource(newSource); - }, - [updateSwapSource] - ); - - const sheetHeightWithoutKeyboard = (android ? 275 : 245) + (swapSupportsFlashbots ? 55 : 0); - - const sheetHeightWithKeyboard = sheetHeightWithoutKeyboard + keyboardHeight + (deviceUtils.isSmallPhone ? 30 : 0); - - useEffect(() => { - setParams({ longFormHeight: sheetHeightWithKeyboard }); - }, [sheetHeightWithKeyboard, setParams]); - - const resetToDefaults = useCallback(() => { - slippageRef?.current?.reset(); - settingsChangeFlashbotsEnabled(false); - updateSource(Source.AggregatorRainbow); - }, [settingsChangeFlashbotsEnabled, updateSource]); - - const openFlashbotsExplainer = () => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - type: 'flashbots', - }); - }; - - return ( - - - - - - - {lang.t('exchange.settings')} - - - {swapSupportsFlashbots && ( - - - - - {lang.t('exchange.use_flashbots')} - - {' 􀅵'} - - - - - - - - - )} - - - - - - - - - - - - - {lang.t('exchange.use_defaults')} - - - - - - - { - ios && slippageRef?.current?.blur(); - goBack(); - }} - style={{ maxWidth: 130 }} - > - - - - {lang.t('exchange.done')} - - - - - - - - - - ); -} diff --git a/src/components/fab/ExchangeFab.js b/src/components/fab/ExchangeFab.js deleted file mode 100644 index 744d69c8f31..00000000000 --- a/src/components/fab/ExchangeFab.js +++ /dev/null @@ -1,69 +0,0 @@ -import lang from 'i18n-js'; -import React, { useCallback } from 'react'; -import { delayNext } from '../../hooks/useMagicAutofocus'; -import { useNavigation } from '../../navigation/Navigation'; -import { lightModeThemeColors } from '../../styles/colors'; -import { Text } from '../text'; -import FloatingActionButton from './FloatingActionButton'; -import { enableActionsOnReadOnlyWallet } from '@/config/debug'; -import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; -import { useSwapCurrencyHandlers } from '@/hooks'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { magicMemo, watchingAlert } from '@/utils'; - -const FabShadow = [ - [0, 10, 30, lightModeThemeColors.shadow, 0.8], - [0, 5, 15, lightModeThemeColors.swapPurple, 1], -]; - -const FabIcon = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.whiteLabel, - letterSpacing: 'zero', - size: 24, - weight: 'semibold', -}))({}); - -const ExchangeFab = ({ disabled, isReadOnlyWallet, ...props }) => { - const { navigate } = useNavigation(); - const { colors } = useTheme(); - - const { updateInputCurrency } = useSwapCurrencyHandlers({ - shouldUpdate: false, - type: ExchangeModalTypes.swap, - }); - - const handlePress = useCallback(() => { - if (!isReadOnlyWallet || enableActionsOnReadOnlyWallet) { - android && delayNext(); - navigate(Routes.EXCHANGE_MODAL, { - fromDiscover: true, - params: { - fromDiscover: true, - onSelectCurrency: updateInputCurrency, - title: lang.t('swap.modal_types.swap'), - type: CurrencySelectionTypes.input, - }, - screen: Routes.CURRENCY_SELECT_SCREEN, - }); - } else { - watchingAlert(); - } - }, [isReadOnlyWallet, navigate, updateInputCurrency]); - - return ( - - 􀖅 - - ); -}; - -export default magicMemo(ExchangeFab, ['disabled', 'isReadOnlyWallet']); diff --git a/src/components/fab/FabWrapper.js b/src/components/fab/FabWrapper.js deleted file mode 100644 index f988107eadc..00000000000 --- a/src/components/fab/FabWrapper.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, { createElement } from 'react'; -import { safeAreaInsetValues } from '../../utils'; -import { FlexItem, RowWithMargins } from '../layout'; -import ExchangeFab from './ExchangeFab'; -import SendFab from './SendFab'; -import styled from '@/styled-thing'; - -export const FabWrapperBottomPosition = 21 + safeAreaInsetValues.bottom; -export const FabWrapperItemMargin = 15; - -const FabWrapperRow = styled(RowWithMargins).attrs({ margin: 13 })({ - bottom: ({ isEditMode }) => (isEditMode ? -60 : FabWrapperBottomPosition), - position: 'absolute', - right: FabWrapperItemMargin, - zIndex: 2, -}); - -export default function FabWrapper({ children, disabled, fabs = [ExchangeFab, SendFab], isCoinListEdited, isReadOnlyWallet, ...props }) { - const renderFab = React.useCallback( - (fab, index) => { - const id = `${index}`; - return createElement(fab, { - isReadOnlyWallet, - key: `fab-${id}`, - ...props, - }); - }, - [props, isReadOnlyWallet] - ); - return ( - - {children} - {!disabled && ( - - {fabs.map(renderFab)} - - )} - - ); -} diff --git a/src/components/fab/FloatingActionButton.js b/src/components/fab/FloatingActionButton.js index b82cf04f377..cd566a3da68 100644 --- a/src/components/fab/FloatingActionButton.js +++ b/src/components/fab/FloatingActionButton.js @@ -1,7 +1,7 @@ import React, { useCallback, useMemo } from 'react'; import { darkModeThemeColors } from '../../styles/colors'; import { useTheme } from '../../theme/ThemeContext'; -import { magicMemo } from '../../utils'; +import { magicMemo, safeAreaInsetValues } from '../../utils'; import ButtonPressAnimation, { ScaleButtonZoomableAndroid } from '../animations/ButtonPressAnimation'; import { Centered, InnerBorder } from '../layout'; import styled from '@/styled-thing'; @@ -9,6 +9,7 @@ import { borders, position } from '@/styles'; import ShadowStack from '@/react-native-shadow-stack'; export const FloatingActionButtonSize = 56; +export const FabWrapperBottomPosition = 21 + safeAreaInsetValues.bottom; export const FloatingActionButtonShadow = colors => [ [0, 2, 5, colors.shadow, 0.2], diff --git a/src/components/fab/SendFab.js b/src/components/fab/SendFab.js deleted file mode 100644 index 77ab1450770..00000000000 --- a/src/components/fab/SendFab.js +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useCallback } from 'react'; -import { useNavigation } from '../../navigation/Navigation'; -import { lightModeThemeColors } from '../../styles/colors'; -import { useTheme } from '../../theme/ThemeContext'; -import { Text } from '../text'; -import FloatingActionButton from './FloatingActionButton'; -import { enableActionsOnReadOnlyWallet } from '@/config/debug'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { magicMemo, watchingAlert } from '@/utils'; - -const FabShadow = [ - [0, 10, 30, lightModeThemeColors.shadow, 0.8], - [0, 5, 15, lightModeThemeColors.paleBlue, 1], -]; - -const FabIcon = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.whiteLabel, - letterSpacing: 'zero', - size: 24, - weight: 'semibold', -}))({}); - -const SendFab = ({ disabled, isReadOnlyWallet, ...props }) => { - const { navigate } = useNavigation(); - const { colors } = useTheme(); - - const handlePress = useCallback(() => { - if (!isReadOnlyWallet || enableActionsOnReadOnlyWallet) { - navigate(Routes.SEND_FLOW); - } else { - watchingAlert(); - } - }, [navigate, isReadOnlyWallet]); - - return ( - - 􀈠 - - ); -}; - -export default magicMemo(SendFab, ['disabled', 'isReadOnlyWallet']); diff --git a/src/components/fab/index.js b/src/components/fab/index.js index 9f6597b65a5..06a513c4e6f 100644 --- a/src/components/fab/index.js +++ b/src/components/fab/index.js @@ -1,5 +1,7 @@ -export { default as ExchangeFab } from './ExchangeFab'; export { default as ExtraStates } from './ExtraStates'; -export { default as FabWrapper, FabWrapperBottomPosition } from './FabWrapper'; -export { default as FloatingActionButton, FloatingActionButtonSize } from './FloatingActionButton'; -export { default as SendFab } from './SendFab'; +export { + default as FloatingActionButton, + FloatingActionButtonSize, + FloatingActionButtonShadow, + FabWrapperBottomPosition, +} from './FloatingActionButton'; diff --git a/src/components/fields/BubbleField.js b/src/components/fields/BubbleField.js index e3cc4d7eb17..7f55d739fd2 100644 --- a/src/components/fields/BubbleField.js +++ b/src/components/fields/BubbleField.js @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTheme } from '../../theme/ThemeContext'; import { MiniButton } from '../buttons'; -import { ExchangeInput } from '../exchange'; +import ExchangeInput from '@/components/ExchangeInput'; import { Column, Row } from '../layout'; import { useDimensions } from '@/hooks'; import styled from '@/styled-thing'; diff --git a/src/components/fields/SmallBubbleField.js b/src/components/fields/SmallBubbleField.js index 9c7697f12e2..f9898fe1861 100644 --- a/src/components/fields/SmallBubbleField.js +++ b/src/components/fields/SmallBubbleField.js @@ -1,10 +1,11 @@ import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { useTheme } from '../../theme/ThemeContext'; -import { ExchangeInput } from '../exchange'; +import ExchangeInput from '@/components/ExchangeInput'; import { Column, Row } from '../layout'; import { Text } from '../text'; import { useDimensions } from '@/hooks'; import styled from '@/styled-thing'; + const BubbleInput = styled(ExchangeInput).attrs(({ isSmallPhone, isTinyPhone, theme: { isDarkMode } }) => ({ disableTabularNums: true, keyboardAppearance: isDarkMode ? 'dark' : 'light', diff --git a/src/components/gas/GasSpeedButton.tsx b/src/components/gas/GasSpeedButton.tsx index d1c11840748..48011d8980f 100644 --- a/src/components/gas/GasSpeedButton.tsx +++ b/src/components/gas/GasSpeedButton.tsx @@ -15,19 +15,18 @@ import { Text } from '../text'; import { GasSpeedLabelPager } from '.'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { isL2Chain } from '@/handlers/web3'; -import { add, greaterThan, toFixedDecimals } from '@/helpers/utilities'; -import { getCrossChainTimeEstimate } from '@/utils/crossChainTimeEstimates'; -import { useAccountSettings, useColorForAsset, useGas, usePrevious, useSwapCurrencies } from '@/hooks'; +import { add, convertAmountToNativeDisplayWorklet, greaterThan, toFixedDecimals } from '@/helpers/utilities'; +import { useAccountSettings, useColorForAsset, useGas, usePrevious } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { fonts, fontWithWidth, margin, padding } from '@/styles'; -import { ethereumUtils, gasUtils } from '@/utils'; +import { gasUtils } from '@/utils'; import { IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { ChainId } from '@/chains/types'; -import { chainsGasSpeeds } from '@/chains'; +import { chainsGasSpeeds, chainsNativeAsset } from '@/chains'; import { ThemeContextProps, useTheme } from '@/theme'; import { ParsedAddressAsset } from '@/entities'; import { GasSpeed } from '@/__swaps__/types/gas'; @@ -117,13 +116,9 @@ const GasSpeedPagerCentered = styled(Centered).attrs(() => ({ const TextContainer = styled(Column).attrs(() => ({}))({}); -const TransactionTimeLabel = ({ formatter, isLongWait, theme }: { formatter: () => string; isLongWait: boolean; theme: string }) => { +const TransactionTimeLabel = ({ formatter, theme }: { formatter: () => string; theme: string }) => { const { colors } = useTheme(); - let color = theme === 'dark' ? colors.alpha(darkModeThemeColors.blueGreyDark, 0.6) : colors.alpha(colors.blueGreyDark, 0.6); - - if (isLongWait) { - color = colors.lightOrange; - } + const color = theme === 'dark' ? colors.alpha(darkModeThemeColors.blueGreyDark, 0.6) : colors.alpha(colors.blueGreyDark, 0.6); return (
diff --git a/src/components/sheet/sheet-action-buttons/BuyActionButton.js b/src/components/sheet/sheet-action-buttons/BuyActionButton.js index 7521babd131..8e8929b6f56 100644 --- a/src/components/sheet/sheet-action-buttons/BuyActionButton.js +++ b/src/components/sheet/sheet-action-buttons/BuyActionButton.js @@ -3,17 +3,17 @@ import React, { useCallback } from 'react'; import SheetActionButton from './SheetActionButton'; import { analyticsV2 } from '@/analytics'; import showWalletErrorAlert from '@/helpers/support'; -import { useAccountSettings, useExpandedStateNavigation, useWallets } from '@/hooks'; +import { useWallets } from '@/hooks'; import Routes from '@/navigation/routesNames'; import { useRoute } from '@react-navigation/native'; +import useNavigationForNonReadOnlyWallets from '@/hooks/useNavigationForNonReadOnlyWallets'; -function BuyActionButton({ color: givenColor, asset, ...props }) { +function BuyActionButton({ color: givenColor, ...props }) { const { colors } = useTheme(); const color = givenColor || colors.paleBlue; - const navigate = useExpandedStateNavigation(null, true, asset); + const navigate = useNavigationForNonReadOnlyWallets(); const { isDamaged } = useWallets(); - const { accountAddress } = useAccountSettings(); const { name: routeName } = useRoute(); const handlePress = useCallback(() => { @@ -22,13 +22,13 @@ function BuyActionButton({ color: givenColor, asset, ...props }) { return; } - navigate(Routes.ADD_CASH_SHEET, params => params); + navigate(Routes.ADD_CASH_SHEET); analyticsV2.track(analyticsV2.event.buyButtonPressed, { componentName: 'BuyActionButton', routeName, }); - }, [accountAddress, isDamaged, navigate, routeName]); + }, [isDamaged, navigate, routeName]); return ; } diff --git a/src/components/sheet/sheet-action-buttons/SendActionButton.js b/src/components/sheet/sheet-action-buttons/SendActionButton.js index 6e572903210..d5a8e43e67a 100644 --- a/src/components/sheet/sheet-action-buttons/SendActionButton.js +++ b/src/components/sheet/sheet-action-buttons/SendActionButton.js @@ -1,24 +1,19 @@ import lang from 'i18n-js'; import React, { useCallback } from 'react'; import SheetActionButton from './SheetActionButton'; -import { useExpandedStateNavigation } from '@/hooks'; import Routes from '@/navigation/routesNames'; import { IS_IOS } from '@/env'; +import useNavigationForNonReadOnlyWallets from '@/hooks/useNavigationForNonReadOnlyWallets'; function SendActionButton({ asset, color: givenColor, ...props }) { const { colors } = useTheme(); const color = givenColor || colors.paleBlue; - const navigate = useExpandedStateNavigation(null, false, asset); + const navigate = useNavigationForNonReadOnlyWallets(); const handlePress = useCallback( () => - navigate(Routes.SEND_FLOW, params => { - const updatedParams = { ...params, asset }; - return IS_IOS - ? { - params: updatedParams, - screen: Routes.SEND_SHEET, - } - : { ...updatedParams }; + navigate(Routes.SEND_FLOW, { + asset, + ...(IS_IOS ? { screen: Routes.SEND_SHEET, params: { asset } } : {}), }), [navigate, asset] ); diff --git a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx index ac86628dcea..8e38433a4ab 100644 --- a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx +++ b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx @@ -1,169 +1,123 @@ import lang from 'i18n-js'; import React, { useCallback } from 'react'; import SheetActionButton from './SheetActionButton'; -import { useExpandedStateNavigation, useSwapCurrencyHandlers, useWallets } from '@/hooks'; import Routes from '@/navigation/routesNames'; import { useTheme } from '@/theme'; import { RainbowToken } from '@/entities'; -import { useRemoteConfig } from '@/model/remoteConfig'; -import { useNavigation } from '@/navigation'; -import { SWAPS_V2, useExperimentalFlag, enableActionsOnReadOnlyWallet } from '@/config'; -import { ethereumUtils, watchingAlert } from '@/utils'; +import { ethereumUtils } from '@/utils'; import { userAssetsStore } from '@/state/assets/userAssets'; import { isSameAsset, parseSearchAsset } from '@/__swaps__/utils/assets'; -import assetInputTypes from '@/helpers/assetInputTypes'; +import { SwapAssetType } from '@/__swaps__/types/swap'; import { swapsStore } from '@/state/swaps/swapsStore'; import { InteractionManager } from 'react-native'; import { AddressOrEth, AssetType, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import exchangeModalTypes from '@/helpers/exchangeModalTypes'; import { chainsIdByName, chainsName } from '@/chains'; +import useNavigationForNonReadOnlyWallets from '@/hooks/useNavigationForNonReadOnlyWallets'; type SwapActionButtonProps = { asset: RainbowToken; color: string; - inputType: 'in' | 'out'; + inputType: SwapAssetType; label?: string; - fromDiscover?: boolean; weight?: string; }; -function SwapActionButton({ asset, color: givenColor, inputType, label, fromDiscover, weight = 'heavy', ...props }: SwapActionButtonProps) { +function SwapActionButton({ asset, color: givenColor, inputType, label, weight = 'heavy', ...props }: SwapActionButtonProps) { const { colors } = useTheme(); - const { swaps_v2 } = useRemoteConfig(); - const { navigate } = useNavigation(); - const swapsV2Enabled = useExperimentalFlag(SWAPS_V2); - const { isReadOnlyWallet } = useWallets(); + const navigate = useNavigationForNonReadOnlyWallets(); const color = givenColor || colors.swapPurple; - useSwapCurrencyHandlers({ - defaultInputAsset: inputType === assetInputTypes.in ? asset : null, - defaultOutputAsset: inputType === assetInputTypes.out ? asset : null, - shouldUpdate: true, - type: exchangeModalTypes.swap, - }); - - const old_navigate = useExpandedStateNavigation(inputType, fromDiscover, asset); const goToSwap = useCallback(async () => { - if (swapsV2Enabled || swaps_v2) { - if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { - watchingAlert(); - return; - } - - const chainId = chainsIdByName[asset.network]; - const uniqueId = `${asset.address}_${chainId}`; - const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); - - const parsedAsset = parseSearchAsset({ - assetWithPrice: { - ...asset, - uniqueId, - address: asset.address as AddressOrEth, - type: asset.type as AssetType, - chainId, - chainName: chainsName[chainId], - isNativeAsset: false, - native: {}, - }, - searchAsset: { - ...asset, - uniqueId, - chainId, - chainName: chainsName[chainId], - address: asset.address as AddressOrEth, - highLiquidity: asset.highLiquidity ?? false, - isRainbowCurated: asset.isRainbowCurated ?? false, - isVerified: asset.isVerified ?? false, - mainnetAddress: (asset.mainnet_address ?? '') as AddressOrEth, - networks: asset.networks ?? [], - type: asset.type as AssetType, - }, - userAsset, - }); + const chainId = chainsIdByName[asset.network]; + const uniqueId = `${asset.address}_${chainId}`; + const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); + + const parsedAsset = parseSearchAsset({ + assetWithPrice: { + ...asset, + uniqueId, + address: asset.address as AddressOrEth, + type: asset.type as AssetType, + chainId, + chainName: chainsName[chainId], + isNativeAsset: false, + native: {}, + }, + searchAsset: { + ...asset, + uniqueId, + chainId, + chainName: chainsName[chainId], + address: asset.address as AddressOrEth, + highLiquidity: asset.highLiquidity ?? false, + isRainbowCurated: asset.isRainbowCurated ?? false, + isVerified: asset.isVerified ?? false, + mainnetAddress: (asset.mainnet_address ?? '') as AddressOrEth, + networks: asset.networks ?? [], + type: asset.type as AssetType, + }, + userAsset, + }); - if (inputType === assetInputTypes.in) { - swapsStore.setState({ inputAsset: userAsset || parsedAsset }); + if (inputType === SwapAssetType.inputAsset) { + swapsStore.setState({ inputAsset: userAsset || parsedAsset }); - const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork({ chainId }); - if (nativeAssetForChain && !isSameAsset({ address: nativeAssetForChain.address as AddressOrEth, chainId }, parsedAsset)) { - const userOutputAsset = userAssetsStore.getState().getUserAsset(`${nativeAssetForChain.address}_${chainId}`); + const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork({ chainId }); + if (nativeAssetForChain && !isSameAsset({ address: nativeAssetForChain.address as AddressOrEth, chainId }, parsedAsset)) { + const userOutputAsset = userAssetsStore.getState().getUserAsset(`${nativeAssetForChain.address}_${chainId}`); - if (userOutputAsset) { - swapsStore.setState({ outputAsset: userOutputAsset }); - } else { - const outputAsset = { - ...nativeAssetForChain, - uniqueId: `${nativeAssetForChain.address}_${chainId}`, - chainId, - chainName: chainsName[chainId], - address: nativeAssetForChain.address as AddressOrEth, - type: nativeAssetForChain.type as AssetType, - mainnetAddress: nativeAssetForChain.mainnet_address as AddressOrEth, - networks: nativeAssetForChain.networks, - colors: { - primary: nativeAssetForChain.colors?.primary, - fallback: nativeAssetForChain.colors?.fallback || undefined, // Ensure fallback is either string or undefined - }, - highLiquidity: nativeAssetForChain.highLiquidity ?? false, - isRainbowCurated: nativeAssetForChain.isRainbowCurated ?? false, - isVerified: nativeAssetForChain.isVerified ?? false, - native: {} as ParsedSearchAsset['native'], - balance: { - amount: nativeAssetForChain.balance?.amount ?? '0', - display: nativeAssetForChain.balance?.display ?? '0', - }, - isNativeAsset: true, - price: { - value: nativeAssetForChain.price?.value ?? 0, - relative_change_24h: nativeAssetForChain.price?.relative_change_24h ?? 0, - }, - } satisfies ParsedSearchAsset; + if (userOutputAsset) { + swapsStore.setState({ outputAsset: userOutputAsset }); + } else { + const outputAsset = { + ...nativeAssetForChain, + uniqueId: `${nativeAssetForChain.address}_${chainId}`, + chainId, + chainName: chainsName[chainId], + address: nativeAssetForChain.address as AddressOrEth, + type: nativeAssetForChain.type as AssetType, + mainnetAddress: nativeAssetForChain.mainnet_address as AddressOrEth, + networks: nativeAssetForChain.networks, + colors: { + primary: nativeAssetForChain.colors?.primary, + fallback: nativeAssetForChain.colors?.fallback || undefined, // Ensure fallback is either string or undefined + }, + highLiquidity: nativeAssetForChain.highLiquidity ?? false, + isRainbowCurated: nativeAssetForChain.isRainbowCurated ?? false, + isVerified: nativeAssetForChain.isVerified ?? false, + native: {} as ParsedSearchAsset['native'], + balance: { + amount: nativeAssetForChain.balance?.amount ?? '0', + display: nativeAssetForChain.balance?.display ?? '0', + }, + isNativeAsset: true, + price: { + value: nativeAssetForChain.price?.value ?? 0, + relative_change_24h: nativeAssetForChain.price?.relative_change_24h ?? 0, + }, + } satisfies ParsedSearchAsset; - swapsStore.setState({ outputAsset }); - } + swapsStore.setState({ outputAsset }); } + } + } else { + const largestBalanceSameChainUserAsset = userAssetsStore + .getState() + .getUserAssets() + .find(userAsset => userAsset.chainId === chainId && userAsset.address !== asset.address); + if (largestBalanceSameChainUserAsset) { + swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); } else { - const largestBalanceSameChainUserAsset = userAssetsStore - .getState() - .getUserAssets() - .find(userAsset => userAsset.chainId === chainId && userAsset.address !== asset.address); - if (largestBalanceSameChainUserAsset) { - swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); - } else { - swapsStore.setState({ inputAsset: null }); - } - swapsStore.setState({ outputAsset: parsedAsset }); + swapsStore.setState({ inputAsset: null }); } - - InteractionManager.runAfterInteractions(() => { - navigate(Routes.SWAP); - }); - - return; + swapsStore.setState({ outputAsset: parsedAsset }); } - old_navigate(Routes.EXCHANGE_MODAL, (params: any) => { - if (params.outputAsset) { - return { - params: { - defaultOutputAsset: asset, - params: { - outputAsset: asset, - }, - }, - screen: Routes.MAIN_EXCHANGE_SCREEN, - }; - } else { - return { - params: { - defaultInputAsset: asset, - }, - screen: Routes.MAIN_EXCHANGE_SCREEN, - }; - } + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); }); - }, [asset, inputType, isReadOnlyWallet, navigate, old_navigate, swapsV2Enabled, swaps_v2]); + }, [asset, inputType, navigate]); return ( = { [REMOTE_CARDS]: { settings: true, value: false }, [POINTS_NOTIFICATIONS_TOGGLE]: { settings: true, value: false }, [DAPP_BROWSER]: { settings: true, value: !!IS_TEST }, - [SWAPS_V2]: { settings: true, value: !!IS_TEST }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, [FEATURED_RESULTS]: { settings: true, value: false }, diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index 9332fa73eb0..d814320dd6a 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -85,8 +85,6 @@ const getCrosschainSwapDefaultGasLimit = (tradeDetails: CrosschainQuote) => trad const getCrosschainSwapRainbowDefaultGasLimit = (chainId: ChainId) => ethereumUtils.getBasicSwapGasLimit(Number(chainId)) * EXTRA_GAS_PADDING; -export const getCrosschainSwapServiceTime = (tradeDetails: CrosschainQuote) => tradeDetails?.routes?.[0]?.serviceTime; - export const getDefaultGasLimitForTrade = (tradeDetails: Quote, chainId: ChainId): number => { const allowsPermit = chainId === ChainId.mainnet && ALLOWS_PERMIT[tradeDetails?.sellTokenAddress?.toLowerCase() as keyof PermitSupportedTokenList]; diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index be7293fa56b..6626776c55a 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -20,45 +20,6 @@ const tokenSearchApi = new RainbowFetchClient({ timeout: 30000, }); -export const swapSearch = async (searchParams: { - chainId: number; - fromChainId?: number | ''; - keys: TokenSearchUniswapAssetKey[]; - list: TokenSearchTokenListId; - threshold: TokenSearchThreshold; - query: string; -}) => { - const queryParams: { - keys: TokenSearchUniswapAssetKey[]; - list: TokenSearchTokenListId; - threshold: TokenSearchThreshold; - query?: string; - fromChainId?: number; - } = { - keys: searchParams.keys, - list: searchParams.list, - threshold: searchParams.threshold, - query: searchParams.query, - }; - if (searchParams.fromChainId) { - queryParams.fromChainId = searchParams.fromChainId; - } - try { - if (isAddress(searchParams.query) && !searchParams.fromChainId) { - // @ts-ignore - params.keys = `networks.${params.chainId}.address`; - } - const url = `/${searchParams.chainId}/?${qs.stringify(queryParams)}`; - const tokenSearch = await tokenSearchApi.get(url); - return { ...tokenSearch.data?.data, chainId: searchParams.chainId }; - } catch (e: any) { - logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { - query: searchParams.query, - message: e.message, - }); - } -}; - export const tokenSearch = async (searchParams: { chainId: number; fromChainId?: number | ''; @@ -112,21 +73,3 @@ export const tokenSearch = async (searchParams: { return []; } }; - -export const walletFilter = async (params: { addresses: EthereumAddress[]; fromChainId: number; toChainId: number }) => { - try { - const { addresses, fromChainId, toChainId } = params; - const filteredAddresses = await tokenSearchApi.post(`/${fromChainId}`, { - addresses, - toChainId, - }); - return filteredAddresses?.data?.data || []; - } catch (e: any) { - logger.error(new RainbowError(`[tokenSearch]: An error occurred while filter wallet addresses`), { - toChainId: params.toChainId, - fromChainId: params.fromChainId, - message: e.message, - }); - throw e; - } -}; diff --git a/src/helpers/assetInputTypes.ts b/src/helpers/assetInputTypes.ts deleted file mode 100644 index d874f078f95..00000000000 --- a/src/helpers/assetInputTypes.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - in: 'in', - out: 'out', -}; diff --git a/src/screens/transaction-details/helpers/checkForPendingSwap.ts b/src/helpers/checkForPendingSwap.ts similarity index 100% rename from src/screens/transaction-details/helpers/checkForPendingSwap.ts rename to src/helpers/checkForPendingSwap.ts diff --git a/src/helpers/ens.ts b/src/helpers/ens.ts index d00fbd56622..d3611b2beb1 100644 --- a/src/helpers/ens.ts +++ b/src/helpers/ens.ts @@ -606,17 +606,11 @@ const formatEstimatedNetworkFee = ( }; }; -const formatTotalRegistrationCost = (wei: string, nativeCurrency: any, nativeAssetPrice: any, skipDecimals = false) => { +const formatTotalRegistrationCost = (wei: string, nativeCurrency: any, nativeAssetPrice: any) => { const networkFeeInEth = fromWei(wei); const eth = handleSignificantDecimals(networkFeeInEth, 3); - const { amount, display } = convertAmountAndPriceToNativeDisplay( - networkFeeInEth, - nativeAssetPrice, - nativeCurrency, - undefined, - skipDecimals - ); + const { amount, display } = convertAmountAndPriceToNativeDisplay(networkFeeInEth, nativeAssetPrice, nativeCurrency); return { amount, @@ -651,13 +645,11 @@ const formatRentPrice = (rentPrice: BigNumberish, duration: number, nativeCurren const rentPricePerYear = getRentPricePerYear(rentPriceInETH, duration); const rentPricePerYearInWei = divide(rentPrice.toString(), duration); - const { amount, display } = convertAmountAndPriceToNativeDisplay(rentPriceInETH, nativeAssetPrice, nativeCurrency, undefined, true); + const { amount, display } = convertAmountAndPriceToNativeDisplay(rentPriceInETH, nativeAssetPrice, nativeCurrency); const { display: displayPerYear, amount: amountPerYear } = convertAmountAndPriceToNativeDisplay( rentPricePerYear, nativeAssetPrice, - nativeCurrency, - undefined, - true + nativeCurrency ); return { diff --git a/src/helpers/exchangeModalTypes.ts b/src/helpers/exchangeModalTypes.ts deleted file mode 100644 index a249c5109c8..00000000000 --- a/src/helpers/exchangeModalTypes.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default { - swap: 'swap', -}; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 4a38d19659d..e2a62a7b9da 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -2,7 +2,6 @@ import * as StatusBarHelper from './statusBarHelper'; export { default as BiometryTypes } from './biometryTypes'; export { default as CurrencySelectionTypes } from './currencySelectionTypes'; -export { default as ExchangeModalTypes } from './exchangeModalTypes'; export { default as isKeyboardOpen } from './isKeyboardOpen'; export { default as isReanimatedAvailable } from './isReanimatedAvailable'; export { default as TokenSectionTypes } from './tokenSectionTypes'; diff --git a/src/helpers/utilities.ts b/src/helpers/utilities.ts index 47eb112d4a2..c3797474d49 100644 --- a/src/helpers/utilities.ts +++ b/src/helpers/utilities.ts @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'; import currency from 'currency.js'; import { isNil } from 'lodash'; import { supportedNativeCurrencies } from '@/references'; +import { divWorklet, lessThanWorklet, orderOfMagnitudeWorklet, powWorklet } from '@/safe-math/SafeMath'; type BigNumberish = number | string | BigNumber; @@ -205,6 +206,24 @@ export const abbreviateNumber = (number: number, decimals = 1): string => { return prefix.toFixed(decimals).replace(/\.0$/, '') + suffix; }; +export const handleSignificantDecimalsWorklet = (value: number | string, decimals: number, buffer = 3): string => { + 'worklet'; + let dec; + + if (lessThanWorklet(value, 1)) { + const orderOfMagnitude = orderOfMagnitudeWorklet(value); + const sigDigitsWithBuffer = -orderOfMagnitude - 1 + buffer; + dec = Math.min(sigDigitsWithBuffer, 8); + } else { + dec = Math.min(decimals, buffer); + } + return Number(value).toLocaleString('en-US', { + useGrouping: true, + minimumFractionDigits: 2, + maximumFractionDigits: dec, + }); +}; + export const handleSignificantDecimals = ( value: BigNumberish, decimals: number, @@ -238,11 +257,10 @@ export const convertAmountAndPriceToNativeDisplay = ( amount: BigNumberish, priceUnit: BigNumberish, nativeCurrency: keyof nativeCurrencyType, - buffer?: number, - skipDecimals = false + useThreshold = false ): { amount: string; display: string } => { const nativeBalanceRaw = convertAmountToNativeAmount(amount, priceUnit); - const nativeDisplay = convertAmountToNativeDisplay(nativeBalanceRaw, nativeCurrency, buffer, skipDecimals); + const nativeDisplay = convertAmountToNativeDisplayWorklet(nativeBalanceRaw, nativeCurrency, useThreshold); return { amount: nativeBalanceRaw, display: nativeDisplay, @@ -256,11 +274,49 @@ export const convertRawAmountToNativeDisplay = ( rawAmount: BigNumberish, assetDecimals: number, priceUnit: BigNumberish, - nativeCurrency: keyof nativeCurrencyType, - buffer?: number + nativeCurrency: keyof nativeCurrencyType ) => { const assetBalance = convertRawAmountToDecimalFormat(rawAmount, assetDecimals); - return convertAmountAndPriceToNativeDisplay(assetBalance, priceUnit, nativeCurrency, buffer); + return convertAmountAndPriceToNativeDisplay(assetBalance, priceUnit, nativeCurrency); +}; + +/** + * @worklet + * @desc convert from raw amount to decimal format + */ +export const convertRawAmountToDecimalFormatWorklet = (value: number | string, decimals = 18): string => { + 'worklet'; + return divWorklet(value, powWorklet(10, decimals)); +}; + +/** + * @desc convert from amount value to display formatted string + */ +export const convertAmountToBalanceDisplayWorklet = ( + value: number | string, + asset: { decimals: number; symbol?: string }, + buffer?: number +) => { + 'worklet'; + const decimals = asset?.decimals ?? 18; + const display = handleSignificantDecimalsWorklet(value, decimals, buffer); + return `${display} ${asset?.symbol || ''}`; +}; + +/** + * @worklet + * @desc convert from raw amount to balance object + */ +export const convertRawAmountToBalanceWorklet = (value: number | string, asset: { decimals: number; symbol?: string }, buffer?: number) => { + 'worklet'; + const decimals = asset?.decimals ?? 18; + + const assetBalance = convertRawAmountToDecimalFormatWorklet(value, decimals); + + return { + amount: assetBalance, + display: convertAmountToBalanceDisplayWorklet(assetBalance, asset, buffer), + }; }; /** @@ -325,6 +381,42 @@ export const convertBipsToPercentage = (value: BigNumberish | null, decimals = 2 return new BigNumber(value || 0).shiftedBy(-2).toFixed(decimals); }; +/** + * @desc convert from amount value to display formatted string + */ +export const convertAmountToNativeDisplayWorklet = ( + value: number | string, + nativeCurrency: keyof nativeCurrencyType, + useThreshold = false, + ignoreAlignment = false +) => { + 'worklet'; + + const nativeSelected = supportedNativeCurrencies?.[nativeCurrency]; + const { alignment, decimals: rawDecimals, symbol } = nativeSelected; + const decimals = Math.min(rawDecimals, 6); + + const valueNumber = Number(value); + const threshold = decimals < 4 ? 0.01 : 0.0001; + let thresholdReached = false; + + if (useThreshold && valueNumber < threshold) { + thresholdReached = true; + } + + const nativeValue = thresholdReached + ? threshold + : valueNumber.toLocaleString('en-US', { + useGrouping: true, + minimumFractionDigits: nativeCurrency === 'ETH' ? undefined : decimals, + maximumFractionDigits: decimals, + }); + + const nativeDisplay = `${thresholdReached ? '<' : ''}${alignment === 'left' || ignoreAlignment ? symbol : ''}${nativeValue}${!ignoreAlignment && alignment === 'right' ? symbol : ''}`; + + return nativeDisplay; +}; + /** * @desc convert from amount value to display formatted string */ @@ -461,3 +553,23 @@ export const getFormattedTimeQuantity = (ms: number, maxUnits?: number): string .slice(0, maxUnits) .join(' '); }; + +const decimalSeparator = '.'; +const lessThanPrefix = '<'; + +export const formatNumber = (value: string, options?: { decimals?: number }) => { + if (!+value) return `0${decimalSeparator}0`; + if (+value < 0.0001) return `${lessThanPrefix}0${decimalSeparator}0001`; + + const [whole, fraction = ''] = value.split(decimalSeparator); + const decimals = options?.decimals; + const paddedFraction = `${fraction.padEnd(decimals || 4, '0')}`; + + if (decimals) { + if (decimals === 0) return whole; + return `${whole}${decimalSeparator}${paddedFraction.slice(0, decimals)}`; + } + + if (+whole > 0) return `${whole}${decimalSeparator}${paddedFraction.slice(0, 2)}`; + return `0${decimalSeparator}${paddedFraction.slice(0, 4)}`; +}; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 2f401dd5695..b7bc96cb3d2 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -18,8 +18,7 @@ export { default as useCoinListEditOptions, useCoinListFinishEditingOptions } fr export { default as useCollectible } from './useCollectible'; export { default as useColorForAsset } from './useColorForAsset'; export { default as useContacts } from './useContacts'; -// @ts-expect-error ts-migrate(1205) FIXME: Re-exporting a type when the '--isolatedModules' f... Remove this comment to see the full error message -export { default as useDimensions, DeviceDimensions } from './useDimensions'; +export { default as useDimensions, type DeviceDimensions } from './useDimensions'; export { default as useDeleteWallet } from './useDeleteWallet'; export { default as useEffectDebugger } from './useEffectDebugger'; export { default as useENSLocalTransactions } from './useENSLocalTransactions'; @@ -41,7 +40,6 @@ export { default as useENSRegistrationCosts } from './useENSRegistrationCosts'; export { default as useENSRegistrationForm } from './useENSRegistrationForm'; export { default as useENSSearch } from './useENSSearch'; export { default as useENSUniqueToken } from './useENSUniqueToken'; -export { default as useExpandedStateNavigation } from './useExpandedStateNavigation'; export { default as useExternalWalletSectionsData } from './useExternalWalletSectionsData'; export { default as useFetchHiddenTokens } from './useFetchHiddenTokens'; export { default as useGas } from './useGas'; @@ -79,18 +77,7 @@ export { default as useSendableUniqueTokens } from './useSendableUniqueTokens'; export { default as useSendFeedback } from './useSendFeedback'; export { default as useShakeAnimation } from './useShakeAnimation'; export { default as useShowcaseTokens } from './useShowcaseTokens'; -export { default as usePriceImpactDetails } from './usePriceImpactDetails'; export { default as useStepper } from './useStepper'; -export { default as useSwapAdjustedAmounts } from './useSwapAdjustedAmounts'; -export { default as useSwapCurrencies } from './useSwapCurrencies'; -export { default as useSwapCurrencyHandlers } from './useSwapCurrencyHandlers'; -export { default as useSwapInputRefs } from './useSwapInputRefs'; -export { default as useSwapInputHandlers } from './useSwapInputHandlers'; -export { default as useSwapIsSufficientBalance } from './useSwapIsSufficientBalance'; -export { default as useSwapSettings } from './useSwapSettings'; -export { default as useSwapDerivedOutputs } from './useSwapDerivedOutputs'; -export { default as useSwapDerivedValues } from './useSwapDerivedValues'; -export { default as useSwapRefuel } from './useSwapRefuel'; export { default as useSwitchWallet } from './useSwitchWallet'; export { default as useTimeout } from './useTimeout'; export { default as usePendingTransactions } from './usePendingTransactions'; @@ -108,14 +95,12 @@ export { default as useOnAvatarPress } from './useOnAvatarPress'; export { default as useAdditionalAssetData } from './useAdditionalAssetData'; export { default as useImportingWallet } from './useImportingWallet'; export { default as usePersistentAspectRatio } from './usePersistentAspectRatio'; -export { default as useFeesPanelInputRefs } from './useFeesPanelInputRefs'; export { default as useHardwareBack, useHardwareBackOnFocus } from './useHardwareBack'; -export { default as useSwapCurrencyList } from './useSwapCurrencyList'; -export { default as useSearchCurrencyList } from './useSearchCurrencyList'; export { default as useWalletENSAvatar } from './useWalletENSAvatar'; export { default as useImagePicker } from './useImagePicker'; export { default as useLatestCallback } from './useLatestCallback'; export { default as useHiddenTokens } from './useHiddenTokens'; -export { useSwappableUserAssets } from './useSwappableUserAssets'; export { useAccountAccentColor } from './useAccountAccentColor'; export { useDebounce } from './useDebounce'; +export { default as useSearchCurrencyList } from './useSearchCurrencyList'; +export { default as useFeesPanelInputRefs } from './useFeesPanelInputRefs'; diff --git a/src/hooks/useExpandedStateNavigation.ts b/src/hooks/useExpandedStateNavigation.ts deleted file mode 100644 index 2a3b212b220..00000000000 --- a/src/hooks/useExpandedStateNavigation.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useCallback, useMemo } from 'react'; -import { InteractionManager } from 'react-native'; - -import useWallets from './useWallets'; -import { enableActionsOnReadOnlyWallet } from '@/config'; -import AssetInputTypes from '@/helpers/assetInputTypes'; -import { useNavigation } from '@/navigation'; -import { watchingAlert } from '@/utils'; -import { RainbowToken } from '@/entities'; - -export default function useExpandedStateNavigation( - inputType: (typeof AssetInputTypes)[keyof typeof AssetInputTypes] | null, - fromDiscover = false, - asset: RainbowToken -) { - const { goBack, navigate } = useNavigation(); - const { isReadOnlyWallet } = useWallets(); - - const navigationPayload = useMemo(() => { - switch (inputType) { - case AssetInputTypes.in: - return { - fromDiscover, - inputAsset: asset, - }; - case AssetInputTypes.out: - return { - fromDiscover, - outputAsset: asset, - }; - default: - return { asset }; - } - }, [asset, fromDiscover, inputType]); - - return useCallback( - (routeName: string, traverseParams: any) => { - if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { - watchingAlert(); - return; - } - - InteractionManager.runAfterInteractions(goBack); - InteractionManager.runAfterInteractions(() => { - setTimeout(() => navigate(routeName, traverseParams(navigationPayload)), 50); - }); - }, - [goBack, isReadOnlyWallet, navigate, navigationPayload] - ); -} diff --git a/src/hooks/useFeesPanelInputRefs.ts b/src/hooks/useFeesPanelInputRefs.ts index 1a11b3ebc58..08ccbed8df2 100644 --- a/src/hooks/useFeesPanelInputRefs.ts +++ b/src/hooks/useFeesPanelInputRefs.ts @@ -1,6 +1,6 @@ import { useCallback, useRef } from 'react'; import { TextInput } from 'react-native'; -import useMagicAutofocus from './useMagicAutofocus'; +import useMagicAutofocus from '@/hooks/useMagicAutofocus'; export default function useFeesPanelInputRefs() { const maxBaseFieldRef = useRef(null); diff --git a/src/hooks/useLedgerConnect.ts b/src/hooks/useLedgerConnect.ts index 103370bacb0..775f4917a94 100644 --- a/src/hooks/useLedgerConnect.ts +++ b/src/hooks/useLedgerConnect.ts @@ -20,7 +20,7 @@ export function useLedgerConnect({ errorCallback?: (errorType: LEDGER_ERROR_CODES) => void; }) { const transport = useRef(); - const timer = useRef(undefined); + const timer = useRef | undefined>(undefined); const isReady = useRecoilValue(LedgerIsReadyAtom); const [triggerPollerCleanup, setTriggerPollerCleanup] = useRecoilState(triggerPollerCleanupAtom); const setReadyForPolling = useSetRecoilState(readyForPollingAtom); @@ -64,7 +64,7 @@ export function useLedgerConnect({ /** * Cleans up ledger connection polling */ - const pollerCleanup = (poller: NodeJS.Timer | undefined) => { + const pollerCleanup = (poller: ReturnType | undefined) => { try { if (poller) { logger.debug('[useLedgerConnect]: polling tear down', {}); diff --git a/src/hooks/useMinRefuelAmount.ts b/src/hooks/useMinRefuelAmount.ts deleted file mode 100644 index cedb1b4da44..00000000000 --- a/src/hooks/useMinRefuelAmount.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { ChainId, getMinRefuelAmount } from '@rainbow-me/swaps'; -import { QueryConfigDeprecated, UseQueryData } from '@/react-query'; - -interface MinRefuelAmountParams { - chainId: ChainId; - toChainId: ChainId; -} - -export const minRefuelAmountKey = ({ chainId, toChainId }: MinRefuelAmountParams) => ['min-refuel-amount', chainId, toChainId]; - -const STALE_TIME = 10000; - -export default function useMinRefuelAmount( - { chainId, toChainId }: MinRefuelAmountParams, - config?: QueryConfigDeprecated -) { - return useQuery>( - minRefuelAmountKey({ chainId, toChainId }), - async () => getMinRefuelAmount({ chainId, toChainId }), - { - ...config, - // Data will be stale for 10s to avoid dupe queries - staleTime: STALE_TIME, - } - ); -} diff --git a/src/hooks/useNavigationForNonReadOnlyWallets.ts b/src/hooks/useNavigationForNonReadOnlyWallets.ts new file mode 100644 index 00000000000..4bf216d20ea --- /dev/null +++ b/src/hooks/useNavigationForNonReadOnlyWallets.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; +import { InteractionManager } from 'react-native'; + +import useWallets from './useWallets'; +import { enableActionsOnReadOnlyWallet } from '@/config'; +import { useNavigation } from '@/navigation'; +import { watchingAlert } from '@/utils'; + +export default function useNavigationForNonReadOnlyWallets() { + const { goBack, navigate } = useNavigation(); + const { isReadOnlyWallet } = useWallets(); + + return useCallback( + (routeName: string, params?: any) => { + if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { + watchingAlert(); + return; + } + + InteractionManager.runAfterInteractions(goBack); + InteractionManager.runAfterInteractions(() => { + setTimeout(() => navigate(routeName, params), 50); + }); + }, + [goBack, isReadOnlyWallet, navigate] + ); +} diff --git a/src/hooks/useParamsForExchangeModal.ts b/src/hooks/useParamsForExchangeModal.ts deleted file mode 100644 index d332490f8b6..00000000000 --- a/src/hooks/useParamsForExchangeModal.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { SwapModalField, updateSwapSlippage, updateSwapSource } from '@/redux/swap'; -import { MutableRefObject, useEffect, useState } from 'react'; -import { useSwapInputHandlers } from '@/hooks/index'; -import { SwapMetadata } from '@/raps/references'; -import { useDispatch } from 'react-redux'; -import { useRoute } from '@react-navigation/native'; -import { TextInput } from 'react-native'; -import { disable, enable } from '@/hooks/useMagicAutofocus'; - -// I know this is a bit of dancing with keyboard, sorry! -// Feel free to do it better, I can't. - -export default function ({ - inputFieldRef, - outputFieldRef, - nativeFieldRef, -}: { - inputFieldRef: MutableRefObject; - outputFieldRef: MutableRefObject; - nativeFieldRef: MutableRefObject; -}) { - const { - params: { meta }, - } = useRoute<{ - key: string; - name: string; - params: { meta?: SwapMetadata }; - }>(); - const dispatch = useDispatch(); - - const { updateInputAmount, updateNativeAmount, updateOutputAmount } = useSwapInputHandlers(); - - const [isFillingParams, setIsFillingParams] = useState(false); - - useEffect(() => { - if (meta) { - android && disable(); - setIsFillingParams(true); - if (meta.independentField === SwapModalField.output) { - updateOutputAmount(meta.independentValue); - ios && - setTimeout(() => { - outputFieldRef.current?.blur(); - outputFieldRef.current?.focus(); - }, 100); - } else if (meta.independentField === SwapModalField.input) { - updateInputAmount(meta.independentValue); - ios && - setTimeout(() => { - inputFieldRef.current?.blur(); - inputFieldRef.current?.focus(); - }, 100); - } else if (meta.independentField === SwapModalField.native) { - updateNativeAmount(meta.independentValue); - ios && - setTimeout(() => { - nativeFieldRef.current?.blur(); - nativeFieldRef.current?.focus(); - }, 100); - } - dispatch(updateSwapSource(meta.route)); - dispatch(updateSwapSlippage(meta.slippage)); - android && - setTimeout(() => { - enable(); - if (meta.independentField === SwapModalField.output) { - outputFieldRef.current?.focus(); - outputFieldRef.current?.focus(); - } else if (meta.independentField === SwapModalField.input) { - updateInputAmount(meta.independentValue); - inputFieldRef.current?.focus(); - } else if (meta.independentField === SwapModalField.native) { - nativeFieldRef.current?.focus(); - } - }, 1000); - } - }, [dispatch, inputFieldRef, meta, nativeFieldRef, outputFieldRef, updateInputAmount, updateNativeAmount, updateOutputAmount]); - return isFillingParams; -} diff --git a/src/hooks/usePriceImpactDetails.ts b/src/hooks/usePriceImpactDetails.ts deleted file mode 100644 index 85a68a0238e..00000000000 --- a/src/hooks/usePriceImpactDetails.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { useMemo } from 'react'; -import useAccountSettings from './useAccountSettings'; -import { SwappableAsset } from '@/entities'; - -import { useTheme } from '@/theme'; -import { - convertAmountToNativeDisplay, - convertAmountToPercentageDisplayWithThreshold, - convertRawAmountToNativeDisplay, - divide, - greaterThanOrEqualTo, - subtract, -} from '@/helpers/utilities'; - -import { CrosschainQuote, Quote, SwapType } from '@rainbow-me/swaps'; -import { useNativeAsset } from '@/utils/ethereumUtils'; -import { ChainId } from '@/chains/types'; - -export enum SwapPriceImpactType { - none = 'none', - high = 'high', - severe = 'severe', -} - -const PriceImpactWarningThreshold = 0.05; -const SeverePriceImpactThreshold = 0.1; -export const NO_PRICE_DATA_PERCENTAGE = '100.00%'; - -export default function usePriceImpactDetails( - inputCurrency: SwappableAsset | null, - outputCurrency: SwappableAsset | null, - tradeDetails: CrosschainQuote | Quote | null, - chainId: ChainId = ChainId.mainnet -) { - const { nativeCurrency } = useAccountSettings(); - const { colors } = useTheme(); - - const sellChainId = ( - (tradeDetails as CrosschainQuote)?.sellTokenAsset?.chainId ? (tradeDetails as CrosschainQuote)?.sellTokenAsset?.chainId : chainId - ) as ChainId; - const buyChainId = (outputCurrency?.chainId || chainId) as ChainId; - const sellNativeAsset = useNativeAsset({ chainId: sellChainId }); - const buyNativeAsset = useNativeAsset({ chainId: buyChainId }); - - const isWrapOrUnwrap = useMemo(() => { - if (!tradeDetails) return false; - return tradeDetails.swapType === SwapType.wrap || tradeDetails.swapType === SwapType.unwrap; - }, [buyChainId, tradeDetails]); - - const inputNativeAmount = useMemo(() => { - if (isWrapOrUnwrap) { - if (!tradeDetails?.sellAmount || !inputCurrency?.price?.value) { - return ''; - } - - return convertRawAmountToNativeDisplay( - tradeDetails?.sellAmount?.toString(), - inputCurrency?.decimals || 18, - inputCurrency?.price?.value, - nativeCurrency - ).amount; - } else { - return convertRawAmountToNativeDisplay( - tradeDetails?.sellAmountInEth?.toString() || '', - sellNativeAsset?.decimals || 18, - sellNativeAsset?.price?.value || '0', - nativeCurrency - ).amount; - } - }, [ - isWrapOrUnwrap, - tradeDetails?.sellAmount, - tradeDetails?.sellAmountInEth, - inputCurrency?.price?.value, - inputCurrency?.decimals, - nativeCurrency, - sellNativeAsset?.decimals, - sellNativeAsset?.price?.value, - ]); - - const outputNativeAmount = useMemo(() => { - if (isWrapOrUnwrap) { - if (!tradeDetails?.buyAmount || !inputCurrency?.price?.value) { - return ''; - } - return convertRawAmountToNativeDisplay( - tradeDetails?.buyAmount?.toString(), - inputCurrency?.decimals || 18, - inputCurrency?.price?.value, - nativeCurrency - ).amount; - } else { - return convertRawAmountToNativeDisplay( - tradeDetails?.buyAmountInEth?.toString() || '', - buyNativeAsset?.decimals || 18, - buyNativeAsset?.price?.value || '0', - nativeCurrency - ).amount; - } - }, [ - isWrapOrUnwrap, - tradeDetails?.buyAmount, - tradeDetails?.buyAmountInEth, - inputCurrency?.price?.value, - inputCurrency?.decimals, - nativeCurrency, - buyNativeAsset?.decimals, - buyNativeAsset?.price?.value, - ]); - - const { impactDisplay, priceImpact, percentDisplay } = useMemo(() => { - const nativeAmountImpact = subtract(inputNativeAmount, outputNativeAmount); - const priceImpact = divide(nativeAmountImpact, inputNativeAmount); - const percentDisplay = convertAmountToPercentageDisplayWithThreshold(priceImpact); - const impactDisplay = convertAmountToNativeDisplay(nativeAmountImpact, nativeCurrency); - return { impactDisplay, priceImpact, percentDisplay }; - }, [outputNativeAmount, nativeCurrency, inputNativeAmount]); - - if (greaterThanOrEqualTo(priceImpact, SeverePriceImpactThreshold)) { - return { - priceImpact: { - type: SwapPriceImpactType.severe, - impactDisplay, - color: colors.red, - percentDisplay, - }, - inputNativeAmount, - outputNativeAmount, - }; - } else if (greaterThanOrEqualTo(priceImpact, PriceImpactWarningThreshold)) { - return { - priceImpact: { - type: SwapPriceImpactType.high, - impactDisplay, - color: colors.orange, - percentDisplay, - }, - inputNativeAmount, - outputNativeAmount, - }; - } else { - return { - priceImpact: { - type: SwapPriceImpactType.none, - impactDisplay, - color: colors.green, - percentDisplay, - }, - inputNativeAmount, - outputNativeAmount, - }; - } -} diff --git a/src/hooks/useRainbowFee.js b/src/hooks/useRainbowFee.js deleted file mode 100644 index 6cc0d416bef..00000000000 --- a/src/hooks/useRainbowFee.js +++ /dev/null @@ -1,57 +0,0 @@ -import { ETH_ADDRESS } from '@rainbow-me/swaps'; -import { useEffect, useState } from 'react'; -import { convertRawAmountToDecimalFormat, divide, multiply, subtract } from '@/helpers/utilities'; -import { useAccountSettings, useSwapCurrencies } from '@/hooks'; -import { ethereumUtils } from '@/utils'; - -export default function useRainbowFee({ tradeDetails, chainId }) { - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - const { accountAddress } = useAccountSettings(); - const [nativeAsset, setNativeAsset] = useState(null); - - const rainbowFeePercentage = useMemo(() => { - const convertToNumber = Number(tradeDetails.feePercentageBasisPoints); - return divide(convertToNumber, '1e18'); - }, [tradeDetails]); - - const rainbowFeeNative = useMemo(() => { - // token to ETH - if (nativeAsset && inputCurrency?.price && tradeDetails.sellAmount) { - if (tradeDetails.buyTokenAddress.toLowerCase() === ETH_ADDRESS.toLowerCase()) { - const feeInOutputTokensRawAmount = divide(multiply(tradeDetails.buyAmount, tradeDetails.feePercentageBasisPoints), '1e18'); - - const feeInOutputToken = convertRawAmountToDecimalFormat(feeInOutputTokensRawAmount, outputCurrency?.decimals || 18); - - return (Number(feeInOutputToken) * Number(nativeAsset.price.value)).toFixed(2); - // eth to token or token to token - } else { - const feeInInputTokensRawAmount = subtract(tradeDetails.sellAmount, tradeDetails.sellAmountMinusFees); - - const feeInInputToken = convertRawAmountToDecimalFormat(feeInInputTokensRawAmount, inputCurrency.decimals); - - return (Number(feeInInputToken) * Number(inputCurrency.price.value)).toFixed(2); - } - } - return null; - }, [ - nativeAsset, - inputCurrency.price, - inputCurrency.decimals, - tradeDetails.sellAmount, - tradeDetails.buyTokenAddress, - tradeDetails.buyAmount, - tradeDetails.feePercentageBasisPoints, - tradeDetails.sellAmountMinusFees, - outputCurrency?.decimals, - ]); - - useEffect(() => { - const getNativeAsset = async () => { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); - setNativeAsset(nativeAsset); - }; - !nativeAsset && getNativeAsset(); - }, [nativeAsset, chainId, accountAddress]); - - return { rainbowFeeNative, rainbowFeePercentage }; -} diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index 027dd4cd7aa..f740de45000 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -13,13 +13,12 @@ import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; import { filterList, isLowerCaseMatch } from '@/utils'; import { logger } from '@/logger'; -import useSwapCurrencies from '@/hooks/useSwapCurrencies'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/chains/types'; import { chainsName } from '@/chains'; +import { ChainId } from '@/chains/types'; type swapCurrencyListType = | 'verifiedAssets' @@ -42,7 +41,6 @@ const abcSort = (list: any[], key?: string) => { const searchCurrencyList = async (searchParams: { chainId: number; - fromChainId?: number | ''; searchList: RainbowToken[] | TokenSearchTokenListId; query: string; }) => { @@ -102,15 +100,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - const { inputCurrency } = useSwapCurrencies(); - const previousInputCurrencyChainId = usePrevious(inputCurrency?.chainId); - const inputChainId = inputCurrency?.chainId; - const isCrosschainSearch = useMemo(() => { - if (inputChainId && inputChainId !== searchChainId && crosschainSwapsEnabled && !isDiscover) { - return true; - } - }, [inputChainId, searchChainId, crosschainSwapsEnabled, isDiscover]); - const isFavorite = useCallback( (address: EthereumAddress) => favoriteAddresses.map(a => a?.toLowerCase()).includes(address?.toLowerCase()), [favoriteAddresses] @@ -217,19 +206,17 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main const getCrosschainVerifiedAssetsForNetwork = useCallback( async (chainId: ChainId) => { - const fromChainId = inputChainId !== chainId ? inputChainId : ''; const results = await searchCurrencyList({ searchList: 'verifiedAssets', query: '', chainId, - fromChainId, }); setCrosschainVerifiedAssets(state => ({ ...state, [chainId]: handleSearchResponse(results || []), })); }, - [handleSearchResponse, inputChainId] + [handleSearchResponse] ); const getCrosschainVerifiedAssets = useCallback(async () => { @@ -250,7 +237,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main searchList: assetType, query: searchQuery, chainId: searchChainId, - fromChainId: isCrosschainSearch && inputChainId, }) ) ); @@ -262,7 +248,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main searchList: assetType, query: searchQuery, chainId: searchChainId, - fromChainId: isCrosschainSearch && inputChainId, }) ) ); @@ -274,7 +259,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main searchList: assetType, query: searchQuery, chainId: searchChainId, - fromChainId: isCrosschainSearch && inputChainId, }) ) ); @@ -291,7 +275,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } } }, - [getFavorites, getImportedAsset, handleSearchResponse, searchQuery, searchChainId, inputChainId, isCrosschainSearch] + [getFavorites, getImportedAsset, handleSearchResponse, searchQuery, searchChainId] ); const search = useCallback(async () => { @@ -324,10 +308,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main const wasSearching = usePrevious(searching); const previousSearchQuery = usePrevious(searchQuery); - useEffect(() => { - setFetchingCrosschainAssets(false); - }, [inputChainId]); - useEffect(() => { if (!fetchingCrosschainAssets && crosschainSwapsEnabled) { setFetchingCrosschainAssets(true); @@ -337,12 +317,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main useEffect(() => { const doSearch = async () => { - if ( - (searching && !wasSearching) || - (searching && previousSearchQuery !== searchQuery) || - searchChainId !== previousChainId || - inputCurrency?.chainId !== previousInputCurrencyChainId - ) { + if ((searching && !wasSearching) || (searching && previousSearchQuery !== searchQuery) || searchChainId !== previousChainId) { if (searchChainId === ChainId.mainnet) { search(); slowSearch(); @@ -358,16 +333,13 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main }; doSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.chainId]); + }, [searching, searchQuery, searchChainId]); const { colors } = useTheme(); const currencyList = useMemo(() => { const list = []; - let bridgeAsset = isCrosschainSearch - ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.chainId !== inputCurrency?.chainId) - : null; if (searching) { const importedAsset = importedAssets?.[0]; let verifiedAssetsWithImport = verifiedAssets; @@ -388,16 +360,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } } } - if (inputCurrency?.name && verifiedAssets.length) { - if (bridgeAsset) { - list.push({ - color: colors.networkColors[bridgeAsset.chainId], - data: [bridgeAsset], - key: 'bridgeAsset', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), - }); - } - } + if (favoriteAssets?.length && searchChainId === ChainId.mainnet) { list.push({ color: colors.yellowFavorite, @@ -430,17 +393,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } } else { const curatedAssets = searchChainId === ChainId.mainnet && getCurated(); - if (inputCurrency?.name && isCrosschainSearch && curatedAssets) { - bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); - if (bridgeAsset) { - list.push({ - color: colors.networkColors[bridgeAsset.chainId], - data: [bridgeAsset], - key: 'bridgeAsset', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), - }); - } - } + if (unfilteredFavorites?.length) { list.push({ color: colors.yellowFavorite, @@ -460,18 +413,14 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } return list; }, [ - isCrosschainSearch, verifiedAssets, searching, - inputCurrency?.name, - inputCurrency?.chainId, importedAssets, highLiquidityAssets, lowLiquidityAssets, isFavorite, favoriteAssets, searchChainId, - colors.networkColors, colors.yellowFavorite, getCurated, unfilteredFavorites, diff --git a/src/hooks/useSwapAdjustedAmounts.ts b/src/hooks/useSwapAdjustedAmounts.ts deleted file mode 100644 index 7e702937815..00000000000 --- a/src/hooks/useSwapAdjustedAmounts.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ETH_ADDRESS, Quote } from '@rainbow-me/swaps'; -import lang from 'i18n-js'; -import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; -import { SwapModalField } from '@/redux/swap'; -import { - WETH_ADDRESS, - WDEGEN_DEGEN_CHAIN_ADDRESS, - WMATIC_POLYGON_ADDRESS, - WBNB_BSC_ADDRESS, - WETH_ARBITRUM_ADDRESS, - WETH_ZORA_ADDRESS, -} from '@/references'; -import { fromWei, updatePrecisionToDisplay } from '@/helpers/utilities'; -import { ethereumUtils } from '@/utils'; -import { computeSlippageAdjustedAmounts, Field } from '@/handlers/swap'; - -export default function useSwapAdjustedAmounts(tradeDetails: Quote) { - const inputCurrency = useSelector((state: AppState) => state.swap.inputCurrency); - const outputCurrency = useSelector((state: AppState) => state.swap.outputCurrency); - const inputAsExact = useSelector((state: AppState) => state.swap.independentField !== SwapModalField.output); - const slippageInBips = useSelector((state: AppState) => state.swap.slippageInBips); - const receivedSoldLabel = inputAsExact - ? lang.t('expanded_state.swap_details.minimum_received') - : lang.t('expanded_state.swap_details.maximum_sold'); - const adjustedAmounts = computeSlippageAdjustedAmounts(tradeDetails, slippageInBips); - let amountReceivedSold = inputAsExact ? adjustedAmounts[Field.OUTPUT] : adjustedAmounts[Field.INPUT]; - const address = inputAsExact - ? outputCurrency.mainnet_address || outputCurrency.address - : inputCurrency.mainnet_address || inputCurrency.address; - - // NOTE: This is legacy so i'm not going to update it to obj params - const priceValue = ethereumUtils.getAssetPrice(address); - - // ETH_ADDRESS is a misleading name– this address is used to represent any network's native asset - if ( - // eth <-> weth swap - (tradeDetails.buyTokenAddress === ETH_ADDRESS && tradeDetails.sellTokenAddress === WETH_ADDRESS) || - (tradeDetails.sellTokenAddress === ETH_ADDRESS && tradeDetails.buyTokenAddress === WETH_ADDRESS) || - // matic <-> wmatic swap - (tradeDetails.buyTokenAddress === ETH_ADDRESS && tradeDetails.sellTokenAddress === WMATIC_POLYGON_ADDRESS) || - (tradeDetails.sellTokenAddress === ETH_ADDRESS && tradeDetails.buyTokenAddress === WMATIC_POLYGON_ADDRESS) || - // bnb <-> wbnb swap - (tradeDetails.buyTokenAddress === ETH_ADDRESS && tradeDetails.sellTokenAddress === WBNB_BSC_ADDRESS) || - (tradeDetails.sellTokenAddress === ETH_ADDRESS && tradeDetails.buyTokenAddress === WBNB_BSC_ADDRESS) || - // arb one <-> weth arb one swap - (tradeDetails.buyTokenAddress === ETH_ADDRESS && tradeDetails.sellTokenAddress === WETH_ARBITRUM_ADDRESS) || - (tradeDetails.sellTokenAddress === ETH_ADDRESS && tradeDetails.buyTokenAddress === WETH_ARBITRUM_ADDRESS) || - // zora eth <-> weth swap - (tradeDetails.buyTokenAddress === ETH_ADDRESS && tradeDetails.sellTokenAddress === WETH_ZORA_ADDRESS) || - (tradeDetails.sellTokenAddress === ETH_ADDRESS && tradeDetails.buyTokenAddress === WETH_ZORA_ADDRESS) || - // degen <-> wdegen swap - (tradeDetails.buyTokenAddress === ETH_ADDRESS && tradeDetails.sellTokenAddress === WDEGEN_DEGEN_CHAIN_ADDRESS) || - (tradeDetails.sellTokenAddress === ETH_ADDRESS && tradeDetails.buyTokenAddress === WDEGEN_DEGEN_CHAIN_ADDRESS) - ) { - amountReceivedSold = fromWei(amountReceivedSold.toString()); - } - - const amountReceivedSoldDisplay = updatePrecisionToDisplay( - // @ts-ignore - amountReceivedSold, - priceValue, - !inputAsExact - ); - - return { - amountReceivedSold: amountReceivedSoldDisplay, - receivedSoldLabel, - }; -} diff --git a/src/hooks/useSwapCurrencies.ts b/src/hooks/useSwapCurrencies.ts deleted file mode 100644 index bf3a9f6dc4e..00000000000 --- a/src/hooks/useSwapCurrencies.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useSelector } from 'react-redux'; -import { SwappableAsset } from '@/entities'; -import { AppState } from '@/redux/store'; - -export default function useSwapCurrencies() { - const inputCurrency: SwappableAsset = useSelector((state: AppState) => state.swap.inputCurrency); - const outputCurrency: SwappableAsset = useSelector((state: AppState) => state.swap.outputCurrency); - - return { - inputCurrency, - outputCurrency, - }; -} diff --git a/src/hooks/useSwapCurrencyHandlers.ts b/src/hooks/useSwapCurrencyHandlers.ts deleted file mode 100644 index 9b835ff7a2d..00000000000 --- a/src/hooks/useSwapCurrencyHandlers.ts +++ /dev/null @@ -1,195 +0,0 @@ -import React, { useCallback, useEffect, useMemo } from 'react'; -import { InteractionManager, TextInput } from 'react-native'; -import { useDispatch } from 'react-redux'; -import { delayNext } from './useMagicAutofocus'; -import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; -import { updatePrecisionToDisplay } from '@/helpers/utilities'; -import { useSwapDerivedValues, useSwapInputHandlers } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import { flipSwapCurrencies, updateSwapInputAmount, updateSwapInputCurrency, updateSwapOutputCurrency } from '@/redux/swap'; -import Routes from '@/navigation/routesNames'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; - -const { currentlyFocusedInput, focusTextInput } = TextInput.State; - -export default function useSwapCurrencyHandlers({ - inputNetwork, - outputNetwork, - defaultInputAsset, - defaultOutputAsset, - fromDiscover, - ignoreInitialTypeCheck = false, - inputFieldRef, - nativeFieldRef, - outputFieldRef, - setLastFocusedInputHandle, - shouldUpdate = true, - title, - type, -}: any = {}) { - const dispatch = useDispatch(); - const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - const { navigate, setParams, getParent: dangerouslyGetParent } = useNavigation(); - - const { derivedValues } = useSwapDerivedValues(); - - const { updateInputAmount, updateNativeAmount, updateOutputAmount } = useSwapInputHandlers(); - - const { defaultInputItemInWallet, defaultOutputItem } = useMemo(() => { - if (type === ExchangeModalTypes.swap) { - const defaultInputItemInWallet = defaultInputAsset - ? { - ...defaultInputAsset, - } - : null; - - return { - defaultInputItemInWallet, - defaultOutputItem: defaultOutputAsset ?? null, - }; - } - return { - defaultInputItemInWallet: null, - defaultOutputItem: null, - }; - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - useEffect(() => { - if (shouldUpdate) { - if (defaultInputItemInWallet) { - dispatch(updateSwapInputCurrency(defaultInputItemInWallet, ignoreInitialTypeCheck || crosschainSwapsEnabled)); - } - if (defaultOutputItem) { - dispatch(updateSwapOutputCurrency(defaultOutputItem, ignoreInitialTypeCheck || crosschainSwapsEnabled)); - } - } - }, [defaultInputItemInWallet, dispatch, defaultOutputItem, shouldUpdate, fromDiscover, ignoreInitialTypeCheck, crosschainSwapsEnabled]); - - const flipSwapCurrenciesWithTimeout = useCallback( - (focusToRef: React.RefObject, outputIndependentField = false, independentValue: string | null = null) => { - InteractionManager.runAfterInteractions(() => { - dispatch(flipSwapCurrencies(outputIndependentField, independentValue ? updatePrecisionToDisplay(independentValue) : null)); - setTimeout(() => { - focusTextInput(focusToRef.current); - }, 50); - }); - }, - [dispatch] - ); - - const flipCurrencies = useCallback(() => { - if (inputNetwork !== outputNetwork) { - updateOutputAmount(null); - flipSwapCurrenciesWithTimeout( - nativeFieldRef.current === currentlyFocusedInput() ? nativeFieldRef : inputFieldRef, - false, - derivedValues?.outputAmount - ); - } else if (nativeFieldRef.current === currentlyFocusedInput()) { - updateNativeAmount(null); - updateInputAmount(null); - flipSwapCurrenciesWithTimeout(outputFieldRef, true, derivedValues?.inputAmount); - } else if (inputFieldRef.current === currentlyFocusedInput()) { - updateNativeAmount(null); - updateInputAmount(null); - flipSwapCurrenciesWithTimeout(outputFieldRef, true, derivedValues?.inputAmount); - } else if (outputFieldRef.current === currentlyFocusedInput()) { - updateOutputAmount(null); - flipSwapCurrenciesWithTimeout(inputFieldRef, false, derivedValues?.outputAmount); - } - }, [ - inputNetwork, - outputNetwork, - nativeFieldRef, - inputFieldRef, - outputFieldRef, - updateOutputAmount, - flipSwapCurrenciesWithTimeout, - derivedValues?.outputAmount, - derivedValues?.inputAmount, - updateNativeAmount, - updateInputAmount, - ]); - - const updateInputCurrency = useCallback( - (inputCurrency: any, handleNavigate: any) => { - const newInputCurrency = inputCurrency - ? { - ...inputCurrency, - } - : null; - - dispatch(updateSwapInputCurrency(newInputCurrency, crosschainSwapsEnabled)); - setLastFocusedInputHandle?.(inputFieldRef); - handleNavigate?.(newInputCurrency); - }, - [crosschainSwapsEnabled, dispatch, inputFieldRef, setLastFocusedInputHandle] - ); - - const updateOutputCurrency = useCallback( - (outputCurrency: any, handleNavigate?: (outputCurrency: any) => void) => { - const newOutputCurrency = outputCurrency - ? { - ...outputCurrency, - } - : null; - - dispatch(updateSwapOutputCurrency(newOutputCurrency, crosschainSwapsEnabled)); - setLastFocusedInputHandle?.(inputFieldRef); - handleNavigate?.(newOutputCurrency); - }, - [crosschainSwapsEnabled, dispatch, inputFieldRef, setLastFocusedInputHandle] - ); - - const navigateToSelectInputCurrency = useCallback( - (chainId: number) => { - InteractionManager.runAfterInteractions(() => { - // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'. - dangerouslyGetParent().getState().index = 0; - setParams({ focused: false }); - delayNext(); - navigate(Routes.CURRENCY_SELECT_SCREEN, { - callback: inputFieldRef?.current?.clear, - chainId, - onSelectCurrency: updateInputCurrency, - restoreFocusOnSwapModal: () => setParams({ focused: true }), - title, - type: CurrencySelectionTypes.input, - }); - }); - }, - [dangerouslyGetParent, inputFieldRef, navigate, setParams, title, updateInputCurrency] - ); - - const navigateToSelectOutputCurrency = useCallback( - (chainId: number) => { - InteractionManager.runAfterInteractions(() => { - setParams({ focused: false }); - delayNext(); - navigate(Routes.CURRENCY_SELECT_SCREEN, { - callback: outputFieldRef?.current?.clear, - chainId, - onSelectCurrency: updateOutputCurrency, - restoreFocusOnSwapModal: () => setParams({ focused: true }), - title: 'Receive', - type: CurrencySelectionTypes.output, - }); - }); - }, - [navigate, outputFieldRef, setParams, updateOutputCurrency] - ); - - const updateAndFocusInputAmount = (value: string) => { - dispatch(updateSwapInputAmount(updatePrecisionToDisplay(value), true)); - focusTextInput(inputFieldRef); - }; - - return { - flipCurrencies, - updateAndFocusInputAmount, - navigateToSelectInputCurrency, - navigateToSelectOutputCurrency, - updateInputCurrency, - updateOutputCurrency, - }; -} diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts deleted file mode 100644 index eb08733578d..00000000000 --- a/src/hooks/useSwapCurrencyList.ts +++ /dev/null @@ -1,523 +0,0 @@ -import lang from 'i18n-js'; -import { getAddress, isAddress } from '@ethersproject/address'; -import { EthereumAddress } from '@rainbow-me/swaps'; -import { Contract } from '@ethersproject/contracts'; -import { rankings } from 'match-sorter'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useTheme } from '../theme/ThemeContext'; -import usePrevious from './usePrevious'; -import { RainbowToken, TokenSearchTokenListId } from '@/entities'; -import { swapSearch } from '@/handlers/tokenSearch'; -import { addHexPrefix, getProvider } from '@/handlers/web3'; -import tokenSectionTypes from '@/helpers/tokenSectionTypes'; -import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { filterList, isLowerCaseMatch } from '@/utils'; -import useSwapCurrencies from '@/hooks/useSwapCurrencies'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { IS_TEST } from '@/env'; -import { useFavorites } from '@/resources/favorites'; -import { getUniqueId } from '@/utils/ethereumUtils'; -import { logger } from '@/logger'; -import { ChainId, Network } from '@/chains/types'; -import { chainsName } from '@/chains'; - -type swapCurrencyListType = - | 'verifiedAssets' - | 'highLiquidityAssets' - | 'lowLiquidityAssets' - | 'favoriteAssets' - | 'curatedAssets' - | 'importedAssets'; - -type CrosschainVerifiedAssets = Record< - ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.bsc | ChainId.arbitrum, - RainbowToken[] ->; - -const abcSort = (list: any[], key?: string) => { - return list.sort((a, b) => { - return key ? a[key]?.localeCompare(b[key]) : a?.localeCompare(b); - }); -}; - -const searchCurrencyList = async (searchParams: { - chainId: number; - fromChainId?: number | ''; - searchList: RainbowToken[] | TokenSearchTokenListId; - query: string; -}) => { - const { searchList, query, chainId, fromChainId } = searchParams; - const isAddress = query.match(/^(0x)?[0-9a-fA-F]{40}$/); - const keys: (keyof RainbowToken)[] = isAddress ? ['address'] : ['symbol', 'name']; - const formattedQuery = isAddress ? addHexPrefix(query).toLowerCase() : query; - if (typeof searchList === 'string') { - const threshold = isAddress ? 'CASE_SENSITIVE_EQUAL' : 'CONTAINS'; - if (chainId === ChainId.mainnet && !formattedQuery && searchList !== 'verifiedAssets') { - return []; - } - return swapSearch({ - chainId, - fromChainId, - keys, - list: searchList, - threshold, - query: formattedQuery, - }); - } else { - return filterList(searchList, formattedQuery, keys, { - threshold: isAddress ? rankings.CASE_SENSITIVE_EQUAL : rankings.CONTAINS, - }); - } -}; - -const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainnet, isDiscover = false) => { - const previousChainId = usePrevious(searchChainId); - - const searching = useMemo(() => searchQuery !== '' || ChainId.mainnet !== searchChainId, [searchChainId, searchQuery]); - - const { favorites: favoriteAddresses, favoritesMetadata: favoriteMap } = useFavorites(); - - const curatedMap = rainbowTokenList.CURATED_TOKENS; - const unfilteredFavorites = Object.values(favoriteMap); - - const [loading, setLoading] = useState(true); - const [favoriteAssets, setFavoriteAssets] = useState([]); - const [importedAssets, setImportedAssets] = useState([]); - const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); - const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); - const [verifiedAssets, setVerifiedAssets] = useState([]); - const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); - const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ - [ChainId.mainnet]: [], - [ChainId.optimism]: [], - [ChainId.polygon]: [], - [ChainId.bsc]: [], - [ChainId.arbitrum]: [], - }); - - const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - const { inputCurrency } = useSwapCurrencies(); - const previousInputCurrencyChainId = usePrevious(inputCurrency?.chainId); - const inputChainId = inputCurrency?.chainId; - const isCrosschainSearch = useMemo(() => { - if (inputChainId && inputChainId !== searchChainId && crosschainSwapsEnabled && !isDiscover) { - return true; - } - }, [inputChainId, searchChainId, crosschainSwapsEnabled, isDiscover]); - - const isFavorite = useCallback( - (address: EthereumAddress) => favoriteAddresses.map(a => a?.toLowerCase()).includes(address?.toLowerCase()), - [favoriteAddresses] - ); - const handleSearchResponse = useCallback( - (tokens: RainbowToken[], chainId?: ChainId) => { - // These transformations are necessary for L2 tokens to match our spec - const activeChainId = chainId ? chainId : searchChainId; - return (tokens || []) - .map(token => { - token.address = token.networks?.[activeChainId]?.address || token.address; - - token.chainId = activeChainId; - if (token.networks[ChainId.mainnet]) { - token.mainnet_address = token.networks[ChainId.mainnet].address; - } - token.uniqueId = getUniqueId(token.address, activeChainId); - - return token; - }) - .filter(({ address }) => !isFavorite(address)); - }, - [searchChainId, isFavorite] - ); - - const getCurated = useCallback(() => { - const addresses = favoriteAddresses.map(a => a.toLowerCase()); - return Object.values(curatedMap) - .filter(({ address }) => !addresses.includes(address.toLowerCase())) - .sort((t1, t2) => { - const { address: address1, name: name1 } = t1; - const { address: address2, name: name2 } = t2; - const mainnetPriorityTokens = [ETH_ADDRESS, WETH_ADDRESS, DAI_ADDRESS, USDC_ADDRESS, WBTC_ADDRESS]; - const rankA = mainnetPriorityTokens.findIndex(address => address === address1.toLowerCase()); - const rankB = mainnetPriorityTokens.findIndex(address => address === address2.toLowerCase()); - const aIsRanked = rankA > -1; - const bIsRanked = rankB > -1; - if (aIsRanked) { - if (bIsRanked) { - return rankA > rankB ? -1 : 1; - } - return -1; - } - return bIsRanked ? 1 : name1?.localeCompare(name2); - }) - .map(token => { - return { - ...token, - network: Network.mainnet, - chainId: ChainId.mainnet, - uniqueId: getUniqueId(token.address, ChainId.mainnet), - }; - }); - }, [curatedMap, favoriteAddresses]); - - const getFavorites = useCallback(async () => { - return searching - ? await searchCurrencyList({ - searchList: unfilteredFavorites as RainbowToken[], - query: searchQuery, - chainId: searchChainId, - }) - : unfilteredFavorites; - }, [searchChainId, searchQuery, searching, unfilteredFavorites]); - - const getImportedAsset = useCallback( - async (searchQuery: string, chainId: number): Promise => { - if (searching) { - if (isAddress(searchQuery)) { - const tokenListEntry = rainbowTokenList.RAINBOW_TOKEN_LIST[searchQuery.toLowerCase()]; - if (tokenListEntry) { - return [tokenListEntry]; - } - const provider = getProvider({ chainId }); - const tokenContract = new Contract(searchQuery, erc20ABI, provider); - try { - const [name, symbol, decimals, address] = await Promise.all([ - tokenContract.name(), - tokenContract.symbol(), - tokenContract.decimals(), - getAddress(searchQuery), - ]); - const uniqueId = getUniqueId(address, chainId); - return [ - { - address, - chainId, - decimals, - favorite: false, - highLiquidity: false, - isRainbowCurated: false, - isVerified: false, - name, - networks: { - [chainId]: { - address, - decimals, - }, - }, - symbol, - network: chainsName[chainId], - uniqueId, - } as RainbowToken, - ]; - } catch (e) { - logger.warn('[useSwapCurrencyList]: error getting token data', { error: (e as Error).message }); - return null; - } - } - } - return null; - }, - [searching] - ); - - const getCrosschainVerifiedAssetsForNetwork = useCallback( - async (chainId: ChainId) => { - const fromChainId = inputChainId !== chainId ? inputChainId : ''; - const results = await searchCurrencyList({ - searchList: 'verifiedAssets', - query: '', - chainId, - fromChainId, - }); - setCrosschainVerifiedAssets(state => ({ - ...state, - [chainId]: handleSearchResponse(results, chainId), - })); - }, - [handleSearchResponse, inputChainId] - ); - - const getCrosschainVerifiedAssets = useCallback(async () => { - const crosschainAssetRequests: Promise[] = []; - Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { - crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(Number(chainIdKey))); - }); - await Promise.all(crosschainAssetRequests); - }, [crosschainVerifiedAssets, getCrosschainVerifiedAssetsForNetwork]); - - const getResultsForAssetType = useCallback( - async (assetType: swapCurrencyListType) => { - switch (assetType) { - case 'verifiedAssets': - setVerifiedAssets( - handleSearchResponse( - await searchCurrencyList({ - searchList: assetType, - query: searchQuery, - chainId: searchChainId, - fromChainId: isCrosschainSearch && inputChainId, - }) - ) - ); - break; - case 'highLiquidityAssets': - setHighLiquidityAssets( - handleSearchResponse( - await searchCurrencyList({ - searchList: assetType, - query: searchQuery, - chainId: searchChainId, - fromChainId: isCrosschainSearch && inputChainId, - }) - ) - ); - break; - case 'lowLiquidityAssets': - setLowLiquidityAssets( - handleSearchResponse( - await searchCurrencyList({ - searchList: assetType, - query: searchQuery, - chainId: searchChainId, - fromChainId: isCrosschainSearch && inputChainId, - }) - ) - ); - break; - case 'favoriteAssets': - setFavoriteAssets((await getFavorites()) || []); - break; - case 'importedAssets': { - const importedAssetResult = await getImportedAsset(searchQuery, searchChainId); - if (importedAssetResult) { - setImportedAssets(handleSearchResponse(importedAssetResult)); - } - break; - } - } - }, - [getFavorites, getImportedAsset, handleSearchResponse, searchQuery, searchChainId, inputChainId, isCrosschainSearch] - ); - - const search = useCallback(async () => { - const categories: swapCurrencyListType[] = - searchChainId === ChainId.mainnet - ? ['favoriteAssets', 'highLiquidityAssets', 'verifiedAssets', 'importedAssets'] - : ['verifiedAssets', 'importedAssets']; - setLoading(true); - await Promise.all(categories.map(assetType => getResultsForAssetType(assetType))); - }, [searchChainId, getResultsForAssetType]); - - const slowSearch = useCallback(async () => { - try { - await getResultsForAssetType('lowLiquidityAssets'); - // eslint-disable-next-line no-empty - } catch (e) { - } finally { - setLoading(false); - } - }, [getResultsForAssetType]); - - const clearSearch = useCallback(() => { - getResultsForAssetType('curatedAssets'); - setLowLiquidityAssets([]); - setHighLiquidityAssets([]); - setVerifiedAssets([]); - setImportedAssets([]); - }, [getResultsForAssetType]); - - const wasSearching = usePrevious(searching); - const previousSearchQuery = usePrevious(searchQuery); - - useEffect(() => { - setFetchingCrosschainAssets(false); - }, [inputChainId]); - - useEffect(() => { - if (!fetchingCrosschainAssets && crosschainSwapsEnabled) { - setFetchingCrosschainAssets(true); - getCrosschainVerifiedAssets(); - } - }, [getCrosschainVerifiedAssets, fetchingCrosschainAssets, crosschainSwapsEnabled]); - - useEffect(() => { - const doSearch = async () => { - if ( - (searching && !wasSearching) || - (searching && previousSearchQuery !== searchQuery) || - searchChainId !== previousChainId || - inputCurrency?.chainId !== previousInputCurrencyChainId - ) { - if (searchChainId === ChainId.mainnet) { - search(); - slowSearch(); - } else { - await search(); - setLowLiquidityAssets([]); - setHighLiquidityAssets([]); - setLoading(false); - } - } else { - clearSearch(); - } - }; - doSearch(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.chainId]); - - const { colors } = useTheme(); - - const currencyList = useMemo(() => { - const list = []; - let bridgeAsset = isCrosschainSearch - ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.chainId !== inputCurrency?.chainId) - : null; - if (searching) { - const importedAsset = importedAssets?.[0]; - let verifiedAssetsWithImport = verifiedAssets; - let highLiquidityAssetsWithImport = highLiquidityAssets; - let lowLiquidityAssetsWithoutImport = lowLiquidityAssets; - const verifiedAddresses = verifiedAssets.map(({ address }) => address.toLowerCase()); - const highLiquidityAddresses = verifiedAssets.map(({ address }) => address.toLowerCase()); - // this conditional prevents the imported token from jumping - // sections if verified/highliquidity search responds later - // than the contract checker in getImportedAsset - if (importedAsset && !isFavorite(importedAsset?.address)) { - lowLiquidityAssetsWithoutImport = lowLiquidityAssets.filter(({ address }) => address.toLowerCase() !== importedAsset?.address); - if (importedAsset?.isVerified && !verifiedAddresses.includes(importedAsset?.address.toLowerCase())) { - verifiedAssetsWithImport = [importedAsset, ...verifiedAssets]; - } else { - if (!highLiquidityAddresses.includes(importedAsset?.address.toLowerCase())) { - highLiquidityAssetsWithImport = [importedAsset, ...highLiquidityAssets]; - } - } - } - if (inputCurrency?.name && verifiedAssets.length) { - if (bridgeAsset) { - list.push({ - color: colors.networkColors[bridgeAsset.chainId], - data: [bridgeAsset], - key: 'bridgeAsset', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), - }); - } - } - if (favoriteAssets?.length && searchChainId === ChainId.mainnet) { - list.push({ - color: colors.yellowFavorite, - data: abcSort(favoriteAssets, 'name'), - key: 'favorites', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.favoriteTokenSection}`), - }); - } - if (verifiedAssetsWithImport?.length) { - list.push({ - data: verifiedAssetsWithImport, - key: 'verified', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.verifiedTokenSection}`), - useGradientText: !IS_TEST, - }); - } - if (highLiquidityAssetsWithImport?.length) { - list.push({ - data: highLiquidityAssetsWithImport, - key: 'highLiquidity', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.unverifiedTokenSection}`), - }); - } - if (lowLiquidityAssetsWithoutImport?.length) { - list.push({ - data: lowLiquidityAssetsWithoutImport, - key: 'lowLiquidity', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.lowLiquidityTokenSection}`), - }); - } - } else { - const curatedAssets = searchChainId === ChainId.mainnet && getCurated(); - if (inputCurrency?.name && isCrosschainSearch && curatedAssets) { - bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); - if (bridgeAsset) { - list.push({ - color: colors.networkColors[bridgeAsset.chainId], - data: [bridgeAsset], - key: 'bridgeAsset', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), - }); - } - } - if (unfilteredFavorites?.length) { - list.push({ - color: colors.yellowFavorite, - data: abcSort(unfilteredFavorites, 'name'), - key: 'unfilteredFavorites', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.favoriteTokenSection}`), - }); - } - if (curatedAssets && curatedAssets.length) { - list.push({ - data: curatedAssets, - key: 'curated', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.verifiedTokenSection}`), - useGradientText: !IS_TEST, - }); - } - } - return list; - }, [ - isCrosschainSearch, - verifiedAssets, - searching, - inputCurrency?.name, - inputCurrency?.chainId, - importedAssets, - highLiquidityAssets, - lowLiquidityAssets, - isFavorite, - favoriteAssets, - searchChainId, - colors.networkColors, - colors.yellowFavorite, - getCurated, - unfilteredFavorites, - ]); - - const crosschainExactMatches = useMemo(() => { - if (currencyList.length) return []; - if (!searchQuery) return []; - const exactMatches: RainbowToken[] = []; - Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { - const chainId = Number(chainIdKey); - if (chainId !== searchChainId) { - // including goerli in our networks type is causing this type issue - // @ts-ignore - const exactMatch = crosschainVerifiedAssets[chainId].find((asset: RainbowToken) => { - const symbolMatch = isLowerCaseMatch(asset?.symbol, searchQuery); - const nameMatch = isLowerCaseMatch(asset?.name, searchQuery); - return symbolMatch || nameMatch; - }); - if (exactMatch) { - exactMatches.push({ ...exactMatch, chainId }); - } - } - }); - if (exactMatches?.length) { - return [ - { - data: exactMatches, - key: 'verified', - title: lang.t(`exchange.token_sections.${tokenSectionTypes.crosschainMatchSection}`), - useGradientText: !IS_TEST, - }, - ]; - } - return []; - }, [crosschainVerifiedAssets, currencyList.length, searchChainId, searchQuery]); - - return { - crosschainExactMatches, - swapCurrencyList: currencyList, - swapCurrencyListLoading: loading, - }; -}; - -export default useSwapCurrencyList; diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts deleted file mode 100644 index e56a935be71..00000000000 --- a/src/hooks/useSwapDerivedOutputs.ts +++ /dev/null @@ -1,519 +0,0 @@ -import { - CrosschainQuote, - ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS, - getCrosschainQuote, - getQuote, - Quote, - QuoteError, - QuoteParams, - Source, - SwapType, -} from '@rainbow-me/swaps'; -import { useQuery } from '@tanstack/react-query'; -import { useCallback, useMemo, useState } from 'react'; -// DO NOT REMOVE THESE COMMENTED ENV VARS -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { IS_APK_BUILD, IS_TESTING } from 'react-native-dotenv'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { analytics } from '@/analytics'; -import { EthereumAddress } from '@/entities'; -import { isNativeAsset } from '@/handlers/assets'; -import { - convertAmountFromNativeValue, - convertAmountToNativeAmount, - convertAmountToRawAmount, - convertNumberToString, - convertRawAmountToDecimalFormat, - isZero, - updatePrecisionToDisplay, -} from '@/helpers/utilities'; -import { logger, RainbowError } from '@/logger'; -import store, { AppState } from '@/redux/store'; -import { SwapModalField, updateSwapQuote } from '@/redux/swap'; -import { ethereumUtils } from '@/utils'; -import { useDispatch, useSelector } from 'react-redux'; -import { SwappableAsset } from '../entities/tokens'; -import useAccountSettings from './useAccountSettings'; -import { chainsName } from '@/chains'; - -const SWAP_POLLING_INTERVAL = 5000; - -enum DisplayValue { - input = 'inputAmountDisplay', - output = 'outputAmountDisplay', - native = 'nativeAmountDisplay', -} - -const getSource = (source: Source) => { - if (source === Source.Aggregator0x || source === Source.Aggregotor1inch) return source; - return null; -}; - -const getInputAmount = async ( - outputAmount: string | null, - inputToken: SwappableAsset | null, - outputToken: SwappableAsset, - inputPrice: string | null, - slippage: number, - source: Source, - fromAddress: EthereumAddress -): Promise<{ - inputAmount: string | null; - inputAmountDisplay: string | null; - quoteError?: QuoteError; - tradeDetails: Quote | CrosschainQuote | null; -} | null> => { - if (!inputToken || !outputAmount || isZero(outputAmount) || !outputToken) return null; - - try { - const outputChainId = outputToken.chainId; - - const inputChainId = inputToken.chainId; - - const inputTokenAddress = isNativeAsset(inputToken.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken.address; - - const outputTokenAddress = isNativeAsset(outputToken.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken.address; - - const isCrosschainSwap = inputChainId !== outputChainId; - if (isCrosschainSwap) return null; - - const buyAmount = convertAmountToRawAmount(convertNumberToString(outputAmount), outputToken.decimals); - - logger.debug('[useSwapDerivedOutputs]: ', { - outputToken, - outputChainId, - outputNetwork: outputToken?.network, - outputTokenAddress, - inputToken, - inputChainId, - inputNetwork: inputToken?.network, - inputTokenAddress, - isCrosschainSwap, - }); - - const quoteSource = getSource(source); - const quoteParams: QuoteParams = { - buyAmount, - buyTokenAddress: outputTokenAddress, - chainId: Number(inputChainId), - fromAddress, - sellTokenAddress: inputTokenAddress, - // Add 5% slippage for testing to prevent flaky tests - slippage: IS_TESTING !== 'true' ? slippage : 5, - ...(quoteSource ? { source } : {}), - currency: store.getState().settings.nativeCurrency, - }; - - const rand = Math.floor(Math.random() * 100); - logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); - // Do not deleeeet the comment below 😤 - // @ts-ignore About to get quote - - const quote = await getQuote(quoteParams); - - // if no quote, if quote is error or there's no sell amount - if (!quote || (quote as QuoteError).error || !(quote as Quote).sellAmount) { - if ((quote as QuoteError).error) { - const quoteError = quote as unknown as QuoteError; - logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { - code: quoteError.error_code, - msg: quoteError.message, - }); - return { - inputAmount: null, - inputAmountDisplay: null, - quoteError: quoteError, - tradeDetails: null, - }; - } - return null; - } - const quoteTradeDetails = quote as Quote; - const inputAmount = convertRawAmountToDecimalFormat(quoteTradeDetails.sellAmount.toString(), inputToken.decimals); - - const inputAmountDisplay = inputAmount && inputPrice ? updatePrecisionToDisplay(inputAmount, inputPrice) : null; - - return { - inputAmount, - inputAmountDisplay, - tradeDetails: { - ...quoteTradeDetails, - inputTokenDecimals: inputToken.decimals, - outputTokenDecimals: outputToken.decimals, - }, - }; - } catch (e) { - return null; - } -}; - -const getOutputAmount = async ( - inputAmount: string | null, - inputToken: SwappableAsset, - outputToken: SwappableAsset | null, - slippage: number, - source: Source, - fromAddress: EthereumAddress, - refuel: boolean -): Promise<{ - outputAmount: string | null; - outputAmountDisplay: string | null; - tradeDetails: Quote | CrosschainQuote | null; - quoteError?: QuoteError; -} | null> => { - if (!inputAmount || isZero(inputAmount) || !outputToken) return null; - - try { - const outputChainId = outputToken.chainId; - const buyTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - - const inputChainId = inputToken.chainId; - const sellTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; - - const sellAmount = convertAmountToRawAmount(convertNumberToString(inputAmount), inputToken.decimals); - const isCrosschainSwap = outputChainId !== inputChainId; - - logger.debug(`[useSwapDerivedOutputs]: `, { - outputToken, - outputChainId, - inputToken, - inputChainId, - isCrosschainSwap, - }); - - const quoteSource = getSource(source); - const quoteParams: QuoteParams = { - buyTokenAddress, - chainId: Number(inputChainId), - fromAddress, - sellAmount, - sellTokenAddress, - // Add 5% slippage for testing to prevent flaky tests - slippage: IS_TESTING !== 'true' ? slippage : 5, - ...(quoteSource ? { source } : {}), - toChainId: Number(outputChainId), - refuel, - currency: store.getState().settings.nativeCurrency, - }; - - const rand = Math.floor(Math.random() * 100); - logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); - // Do not deleeeet the comment below 😤 - // @ts-ignore About to get quote - const quote: Quote | CrosschainQuote | QuoteError | null = await (isCrosschainSwap ? getCrosschainQuote : getQuote)(quoteParams); - logger.debug('[useSwapDerivedOutputs]: Got quote', { rand, quote }); - - if (!quote || (quote as QuoteError)?.error || !(quote as Quote)?.buyAmount) { - const quoteError = quote as QuoteError; - if (quoteError.error) { - logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { - code: quoteError.error_code, - msg: quoteError.message, - }); - return { - quoteError, - outputAmount: null, - outputAmountDisplay: null, - tradeDetails: null, - }; - } - return null; - } - - const tradeDetails = quote as Quote | CrosschainQuote; - const outputAmount = convertRawAmountToDecimalFormat(tradeDetails.buyAmount.toString(), outputToken.decimals); - - const outputAmountDisplay = updatePrecisionToDisplay(outputAmount); - - return { - outputAmount, - outputAmountDisplay, - tradeDetails: { - ...tradeDetails, - inputTokenDecimals: inputToken.decimals, - outputTokenDecimals: outputToken.decimals, - }, - }; - } catch (e) { - return null; - } -}; - -const derivedValues: { [key in SwapModalField]: string | null } = { - [SwapModalField.input]: null, - [SwapModalField.native]: null, - [SwapModalField.output]: null, -}; - -const displayValues: { [key in DisplayValue]: string | null } = { - [DisplayValue.input]: null, - [DisplayValue.output]: null, - [DisplayValue.native]: null, -}; - -export default function useSwapDerivedOutputs(type: string) { - const dispatch = useDispatch(); - const { accountAddress } = useAccountSettings(); - - const independentField = useSelector((state: AppState) => state.swap.independentField); - const independentValue = useSelector((state: AppState) => state.swap.independentValue); - const maxInputUpdate = useSelector((state: AppState) => state.swap.maxInputUpdate); - const inputCurrency = useSelector((state: AppState) => state.swap.inputCurrency); - const outputCurrency = useSelector((state: AppState) => state.swap.outputCurrency); - const slippageInBips = useSelector((state: AppState) => state.swap.slippageInBips); - - const source = useSelector((state: AppState) => state.swap.source); - - const [refuel, setRefuel] = useState(false); - - const inputPrice = useMemo(() => { - // NOTE: This is legacy so i'm not going to update it to obj params - const price = ethereumUtils.getAssetPrice(inputCurrency?.mainnet_address ?? inputCurrency?.address); - return price !== 0 ? price : inputCurrency?.price?.value; - }, [inputCurrency]); - - const resetSwapInputs = useCallback(() => { - derivedValues[SwapModalField.input] = null; - derivedValues[SwapModalField.output] = null; - derivedValues[SwapModalField.native] = null; - displayValues[DisplayValue.input] = null; - displayValues[DisplayValue.output] = null; - displayValues[DisplayValue.native] = null; - dispatch( - updateSwapQuote({ - derivedValues, - displayValues, - quoteError: null, - tradeDetails: null, - }) - ); - return { - quoteError: null, - result: { derivedValues, displayValues, tradeDetails: null }, - }; - }, [dispatch]); - - const getTradeDetails = useCallback(async () => { - if (!independentValue) { - return resetSwapInputs(); - } - - if (independentValue === '0.') { - switch (independentField) { - case SwapModalField.input: - displayValues[DisplayValue.input] = independentValue; - displayValues[DisplayValue.output] = null; - displayValues[DisplayValue.native] = null; - break; - case SwapModalField.output: - displayValues[DisplayValue.input] = null; - displayValues[DisplayValue.output] = independentValue; - displayValues[DisplayValue.native] = null; - break; - case SwapModalField.native: - displayValues[DisplayValue.input] = null; - displayValues[DisplayValue.output] = null; - displayValues[DisplayValue.native] = independentValue; - break; - } - - return { - quoteError: null, - result: { - derivedValues, - displayValues, - tradeDetails: null, - }, - }; - } - - logger.debug('[useSwapDerivedOutputs]: Getting trade details', { - independentField, - independentValue, - inputCurrency, - outputCurrency, - inputPrice, - slippageInBips, - source, - refuel, - }); - - let tradeDetails = null; - const slippagePercentage = slippageInBips / 100; - let quoteError: QuoteError | undefined; - - if (independentField === SwapModalField.input) { - const nativeValue = inputPrice ? convertAmountToNativeAmount(independentValue, inputPrice) : null; - derivedValues[SwapModalField.input] = independentValue; - displayValues[DisplayValue.input] = independentValue; - derivedValues[SwapModalField.native] = nativeValue; - displayValues[DisplayValue.native] = nativeValue; - - if (derivedValues[SwapModalField.input] !== independentValue) return; - - const outputAmountData = await getOutputAmount( - independentValue, - inputCurrency, - outputCurrency, - slippagePercentage, - source, - accountAddress, - refuel - ); - - // if original value changed, ignore new quote - if (derivedValues[SwapModalField.input] !== independentValue || !outputAmountData) return null; - - const { outputAmount, outputAmountDisplay, tradeDetails: newTradeDetails, quoteError: newQuoteError } = outputAmountData; - - tradeDetails = newTradeDetails; - quoteError = newQuoteError; - derivedValues[SwapModalField.output] = outputAmount; - displayValues[DisplayValue.output] = outputAmountDisplay; - } else if (independentField === SwapModalField.native) { - const inputAmount = - independentValue && inputPrice ? convertAmountFromNativeValue(independentValue, inputPrice, inputCurrency.decimals) : null; - - const inputAmountDisplay = updatePrecisionToDisplay(inputAmount, inputPrice, true); - derivedValues[SwapModalField.native] = independentValue; - displayValues[DisplayValue.native] = independentValue; - derivedValues[SwapModalField.input] = inputAmount; - displayValues[DisplayValue.input] = inputAmountDisplay; - - if (derivedValues[SwapModalField.native] !== independentValue) return; - - const outputAmountData = await getOutputAmount( - inputAmount, - inputCurrency, - outputCurrency, - slippagePercentage, - source, - accountAddress, - refuel - ); - // if original value changed, ignore new quote - if (derivedValues[SwapModalField.native] !== independentValue || !outputAmountData) return null; - - const { outputAmount, outputAmountDisplay, tradeDetails: newTradeDetails, quoteError: newQuoteError } = outputAmountData; - - tradeDetails = newTradeDetails; - quoteError = newQuoteError; - derivedValues[SwapModalField.output] = outputAmount; - displayValues[DisplayValue.output] = outputAmountDisplay; - } else { - if (!outputCurrency || !inputCurrency) { - return { - quoteError: null, - result: { - derivedValues, - displayValues, - tradeDetails, - }, - }; - } - derivedValues[SwapModalField.output] = independentValue; - displayValues[DisplayValue.output] = independentValue; - - if (derivedValues[SwapModalField.output] !== independentValue) return; - - const inputAmountData = await getInputAmount( - independentValue, - inputCurrency, - outputCurrency, - inputPrice, - slippagePercentage, - source, - accountAddress - ); - - // if original value changed, ignore new quote - if (derivedValues[SwapModalField.output] !== independentValue || !inputAmountData) return null; - - const { inputAmount, inputAmountDisplay, tradeDetails: newTradeDetails, quoteError: newQuoteError } = inputAmountData; - - const nativeValue = inputPrice && inputAmount ? convertAmountToNativeAmount(inputAmount, inputPrice) : null; - - quoteError = newQuoteError; - tradeDetails = newTradeDetails; - derivedValues[SwapModalField.input] = inputAmount; - displayValues[DisplayValue.input] = inputAmountDisplay; - derivedValues[SwapModalField.native] = nativeValue; - displayValues[DisplayValue.native] = nativeValue; - } - - const data = { - derivedValues, - displayValues, - quoteError, - tradeDetails, - }; - - logger.debug('[useSwapDerivedOutputs]: Got trade details', { - data, - }); - - dispatch( - updateSwapQuote({ - derivedValues: data.derivedValues, - displayValues: data.displayValues, - tradeDetails: data.tradeDetails, - }) - ); - analytics.track(`Updated ${type} details`, { - aggregator: data.tradeDetails?.source || '', - inputTokenAddress: inputCurrency?.address || '', - inputTokenName: inputCurrency?.name || '', - inputTokenSymbol: inputCurrency?.symbol || '', - liquiditySources: (data.tradeDetails?.protocols as any[]) || [], - network: chainsName[inputCurrency?.chainId], - outputTokenAddress: outputCurrency?.address || '', - outputTokenName: outputCurrency?.name || '', - outputTokenSymbol: outputCurrency?.symbol || '', - slippage: isNaN(slippagePercentage) ? 'Error calculating slippage.' : slippagePercentage, - type, - }); - - return { quoteError, result: data }; - }, [ - accountAddress, - dispatch, - independentField, - independentValue, - inputCurrency, - inputPrice, - outputCurrency, - resetSwapInputs, - slippageInBips, - source, - type, - refuel, - ]); - const { data, isLoading } = useQuery({ - queryFn: getTradeDetails, - queryKey: [ - 'getTradeDetails', - independentField, - independentValue, - inputCurrency, - outputCurrency, - inputPrice, - maxInputUpdate, - slippageInBips, - source, - refuel, - ], - ...(IS_TESTING !== 'true' ? { refetchInterval: SWAP_POLLING_INTERVAL } : {}), - }); - - return { - loading: isLoading && Boolean(independentValue), - quoteError: data?.quoteError || null, - resetSwapInputs, - setRefuel, - result: data?.result || { - derivedValues, - displayValues, - tradeDetails: null, - }, - }; -} diff --git a/src/hooks/useSwapDerivedValues.ts b/src/hooks/useSwapDerivedValues.ts deleted file mode 100644 index ff2ad398dd3..00000000000 --- a/src/hooks/useSwapDerivedValues.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; -import { SwapModalField } from '@/redux/swap'; - -export default function useSwapDerivedValues() { - const derivedValues: { [key in SwapModalField]: string | null } = useSelector((state: AppState) => state.swap.derivedValues); - - return { - derivedValues, - }; -} diff --git a/src/hooks/useSwapInputHandlers.ts b/src/hooks/useSwapInputHandlers.ts deleted file mode 100644 index ac7684de5c2..00000000000 --- a/src/hooks/useSwapInputHandlers.ts +++ /dev/null @@ -1,71 +0,0 @@ -import lang from 'i18n-js'; -import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { Alert } from '../components/alerts'; -import { isNativeAsset } from '@/handlers/assets'; -import { greaterThan, toFixedDecimals } from '@/helpers/utilities'; -import { useGas } from '@/hooks'; -import { AppState } from '@/redux/store'; -import { updateSwapInputAmount, updateSwapNativeAmount, updateSwapOutputAmount } from '@/redux/swap'; -import { ethereumUtils } from '@/utils'; - -export default function useSwapInputHandlers() { - const dispatch = useDispatch(); - - const { selectedGasFee, l1GasFeeOptimism } = useGas(); - - const inputCurrency = useSelector((state: AppState) => state.swap.inputCurrency); - - const updateMaxInputAmount = useCallback(() => { - const inputCurrencyAddress = inputCurrency?.address; - const inputCurrencyUniqueId = inputCurrency?.uniqueId; - const inputCurrencyChainId = inputCurrency?.chainId; - - const accountAsset = ethereumUtils.getAccountAsset(inputCurrencyUniqueId); - const oldAmount = accountAsset?.balance?.amount ?? '0'; - let newAmount = oldAmount; - if (isNativeAsset(inputCurrencyAddress, inputCurrencyChainId) && accountAsset) { - // this subtracts gas from the balance of the asset - newAmount = toFixedDecimals(ethereumUtils.getBalanceAmount(selectedGasFee, accountAsset, l1GasFeeOptimism), 6); - - if (greaterThan(newAmount, 0)) { - dispatch(updateSwapInputAmount(newAmount)); - } else { - Alert({ - message: lang.t('expanded_state.swap.swap_max_insufficient_alert.message', { symbol: accountAsset.symbol }), - title: lang.t('expanded_state.swap.swap_max_insufficient_alert.title', { symbol: accountAsset.symbol }), - }); - return; - } - } - dispatch(updateSwapInputAmount(newAmount, true)); - }, [dispatch, inputCurrency?.address, inputCurrency?.chainId, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); - - const updateInputAmount = useCallback( - (value: string | null) => { - dispatch(updateSwapInputAmount(value)); - }, - [dispatch] - ); - - const updateNativeAmount = useCallback( - (value: string | null) => { - dispatch(updateSwapNativeAmount(value)); - }, - [dispatch] - ); - - const updateOutputAmount = useCallback( - (value: string | null) => { - dispatch(updateSwapOutputAmount(value)); - }, - [dispatch] - ); - - return { - updateInputAmount, - updateMaxInputAmount, - updateNativeAmount, - updateOutputAmount, - }; -} diff --git a/src/hooks/useSwapInputRefs.ts b/src/hooks/useSwapInputRefs.ts deleted file mode 100644 index ae9e31874e4..00000000000 --- a/src/hooks/useSwapInputRefs.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback, useRef } from 'react'; -import { TextInput } from 'react-native'; -import useMagicAutofocus from './useMagicAutofocus'; -import useSwapCurrencies from './useSwapCurrencies'; - -export default function useSwapInputRefs() { - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - const inputFieldRef = useRef(null); - const nativeFieldRef = useRef(null); - const outputFieldRef = useRef(null); - - const findNextInput = useCallback( - (currentFocusedInputHandle: any) => { - const inputRefHandle = inputFieldRef.current; - const nativeInputRefHandle = nativeFieldRef.current; - const outputRefHandle = outputFieldRef.current; - - const lastFocusedIsInputType = - currentFocusedInputHandle?.current === inputRefHandle || currentFocusedInputHandle?.current === nativeInputRefHandle; - - const lastFocusedIsOutputType = currentFocusedInputHandle?.current === outputRefHandle; - - if (lastFocusedIsInputType && !inputCurrency) { - return outputRefHandle; - } - - if (lastFocusedIsOutputType && !outputCurrency) { - return inputRefHandle; - } - - return currentFocusedInputHandle.current; - }, - [inputCurrency, outputCurrency] - ); - - const { handleFocus, lastFocusedInputHandle, setLastFocusedInputHandle } = useMagicAutofocus(inputFieldRef, findNextInput, true); - - return { - handleFocus, - inputFieldRef, - lastFocusedInputHandle, - nativeFieldRef, - outputFieldRef, - setLastFocusedInputHandle, - }; -} diff --git a/src/hooks/useSwapIsSufficientBalance.ts b/src/hooks/useSwapIsSufficientBalance.ts deleted file mode 100644 index 4dcbe35842d..00000000000 --- a/src/hooks/useSwapIsSufficientBalance.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; -import { greaterThanOrEqualTo } from '@/helpers/utilities'; -import { ethereumUtils } from '@/utils'; - -export default function useSwapIsSufficientBalance(inputAmount: string | null) { - const inputCurrencyUniqueId = useSelector((state: AppState) => state.swap.inputCurrency?.uniqueId); - const type = useSelector((state: AppState) => state.swap.type); - - const isSufficientBalance = useMemo(() => { - if (!inputAmount) return true; - const maxInputBalance = ethereumUtils.getAccountAsset(inputCurrencyUniqueId)?.balance?.amount ?? 0; - return greaterThanOrEqualTo(maxInputBalance, inputAmount); - }, [inputAmount, inputCurrencyUniqueId, type]); - - return isSufficientBalance; -} diff --git a/src/hooks/useSwapRefuel.ts b/src/hooks/useSwapRefuel.ts deleted file mode 100644 index 05bfc52a667..00000000000 --- a/src/hooks/useSwapRefuel.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ParsedAddressAsset, SwappableAsset } from '@/entities'; -import { ethereumUtils } from '@/utils'; -import useMinRefuelAmount from './useMinRefuelAmount'; -import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; -import { add, greaterThan, isZero, lessThan, multiply, subtract } from '@/helpers/utilities'; -import { useEffect, useMemo, useState } from 'react'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { useAccountSettings, useGas } from '.'; -import { isNativeAsset } from '@/handlers/assets'; -import { toWei } from '@/handlers/web3'; -import { ChainId } from '@/chains/types'; - -export enum RefuelState { - 'Add' = 'Add', - 'Deduct' = 'Deduct', - 'Notice' = 'Notice', -} - -export default function useSwapRefuel({ - inputCurrency, - outputCurrency, - tradeDetails, -}: { - inputCurrency: SwappableAsset; - outputCurrency: SwappableAsset; - tradeDetails: Quote | CrosschainQuote | null; -}) { - const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - const { accountAddress } = useAccountSettings(); - const { selectedGasFee } = useGas(); - - const [outputNativeAsset, setOutputNativeAsset] = useState(); - const [inputNativeAsset, setInputNativeAsset] = useState(); - - const { chainId, toChainId, isCrosschainSwap } = useMemo(() => { - const chainId = inputCurrency.chainId; - const toChainId = outputCurrency.chainId; - const isCrosschainSwap = crosschainSwapsEnabled && chainId !== toChainId; - - return { - chainId, - toChainId, - isCrosschainSwap, - }; - }, [crosschainSwapsEnabled, inputCurrency.chainId, outputCurrency.chainId]); - - const { data: minRefuelAmount } = useMinRefuelAmount( - { - chainId: chainId as number, - toChainId: toChainId as number, - }, - { enabled: isCrosschainSwap } - ); - - useEffect(() => { - const getNativeInputOutputAssets = async () => { - if (!chainId || !toChainId || !accountAddress) return; - const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId: toChainId, address: accountAddress }); - const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); - setOutputNativeAsset(outputNativeAsset); - setInputNativeAsset(inputNativeAsset); - }; - getNativeInputOutputAssets(); - }, [accountAddress, toChainId, chainId]); - - const { showRefuelSheet, refuelState } = useMemo(() => { - const swappingToNativeAsset = isNativeAsset(outputCurrency?.address, toChainId); - // // If the swap is going into a native token on the destination chain, in which case we can ignore all refuel functionality - if (swappingToNativeAsset) { - return { showRefuelSheet: false, refuelState: null }; - } - // If its not a crosschain swap then ignore - if (!isCrosschainSwap) { - return { showRefuelSheet: false, refuelState: null }; - } - // If we are swapping to mainnet then ignore - if (toChainId === ChainId.mainnet) return { showRefuelSheet: false, refuelState: null }; - - // Does the user have an existing balance on the output native asset - const hasZeroOutputNativeAssetBalance = isZero(outputNativeAsset?.balance?.amount || 0); - // If its not 0 then ignore - if (!hasZeroOutputNativeAssetBalance) return { showRefuelSheet: false, refuelState: null }; - - // Get minimum refuel amount, this is how much must be used by the refuel swap - const refuelAmount = minRefuelAmount || '0'; - // Check if they have enough of the source native asset once you deduct input amount (if native asset) + gas amount, if the value is < minRefuelAmount we should show the explainer that gives them no options - // Get the gas fee for the swap - const gasFee = multiply(selectedGasFee?.gasFee?.estimatedFee?.value?.amount || '0', 1.5).toString(); - // - If they have >= minRefuelAmount then we should check to see if they swapping native asset and if they will have the minRefuel amount left over after the swap then we offer them the option to add the amount on - // Check the existing source native asset balance - const inputNativeAssetAmount = toWei(inputNativeAsset?.balance?.amount || '0'); - // Check if swapping from native asset - const swappingFromNativeAsset = isNativeAsset(inputCurrency?.address, chainId); - - const gasFeesPlusRefuelAmount = add(gasFee, refuelAmount.toString()); - // - If they wont have enough after the swap of the source native asset then we should offer to deduct some of the input amount into the refuel amount - // If swapping from the native asset we should take that amount into account - if (swappingFromNativeAsset) { - const nativeAmountAfterSwap = subtract(inputNativeAssetAmount, tradeDetails?.sellAmount?.toString() || '0'); - const nativeAmountAfterSwapStillLeft = greaterThan(nativeAmountAfterSwap, 0); - const enoughNativeAssetAfterSwapAndRefuel = lessThan(gasFeesPlusRefuelAmount, nativeAmountAfterSwap); - // if enoughNativeAssetAfterSwapAndRefuel user can refuel without any issue - if (enoughNativeAssetAfterSwapAndRefuel) return { showRefuelSheet: true, refuelState: RefuelState.Add }; - // if user max'ed out - // Show deduct refuel amount modal if enough balance to refuel - if (!enoughNativeAssetAfterSwapAndRefuel && nativeAmountAfterSwapStillLeft) - return { showRefuelSheet: true, refuelState: RefuelState.Deduct }; - // Show notice refuel amount modal if not enough balance to refuel - return { showRefuelSheet: true, refuelState: RefuelState.Notice }; - } - // If the total gas and refuel amount is less than native asset amount balance then show normal refuel screen - // If total gas and refuel is more than the native asset amount the show the continue anyway and dont allow for refuel - const enoughNativeAssetAfterSwapAndRefuel = lessThan(gasFeesPlusRefuelAmount, inputNativeAssetAmount); - - if (enoughNativeAssetAfterSwapAndRefuel) { - return { showRefuelSheet: true, refuelState: RefuelState.Add }; - } - - return { showRefuelSheet: true, refuelState: RefuelState.Notice }; - }, [ - chainId, - inputCurrency?.address, - inputNativeAsset?.balance?.amount, - isCrosschainSwap, - minRefuelAmount, - outputCurrency?.address, - outputNativeAsset?.balance?.amount, - selectedGasFee?.gasFee?.estimatedFee?.value?.amount, - toChainId, - tradeDetails?.sellAmount, - ]); - - return { - showRefuelSheet, - refuelState, - inputNativeAsset, - outputNativeAsset, - minRefuelAmount, - }; -} diff --git a/src/hooks/useSwapSettings.ts b/src/hooks/useSwapSettings.ts deleted file mode 100644 index 8e9d5780414..00000000000 --- a/src/hooks/useSwapSettings.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; -import { Source } from '@/raps/references'; -import { updateSwapSlippage as updateSwapSlippageRedux, updateSwapSource as updateSwapSourceRedux } from '@/redux/swap'; - -export default function useSwapSettings() { - const dispatch = useDispatch(); - - const slippage = useSelector((state: AppState) => state.swap.slippageInBips); - const maxInputUpdate = useSelector((state: AppState) => state.swap.maxInputUpdate); - const flipCurrenciesUpdate = useSelector((state: AppState) => state.swap.flipCurrenciesUpdate); - const currentSource = useSelector((state: AppState) => state.swap.source); - - const updateSwapSlippage = useCallback( - (value: number) => { - dispatch(updateSwapSlippageRedux(value)); - }, - [dispatch] - ); - - const updateSwapSource = useCallback( - (value: Source) => { - dispatch(updateSwapSourceRedux(value)); - }, - [dispatch] - ); - return { - flipCurrenciesUpdate, - maxInputUpdate, - slippageInBips: slippage, - source: currentSource, - updateSwapSlippage, - updateSwapSource, - }; -} diff --git a/src/hooks/useSwappableUserAssets.ts b/src/hooks/useSwappableUserAssets.ts deleted file mode 100644 index e1d501fec48..00000000000 --- a/src/hooks/useSwappableUserAssets.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { SwappableAsset } from '@/entities'; -import { walletFilter } from '@/handlers/tokenSearch'; -import { useCoinListEditOptions } from '@/hooks'; -import { ETH_ADDRESS } from '@/references'; -import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; -import { EthereumAddress, ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS } from '@rainbow-me/swaps'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { ChainId } from '@/chains/types'; -import { supportedSwapChainIds } from '@/chains'; - -type SwappableAddresses = Record; - -export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset }) => { - const { outputCurrency } = params; - const { data: sortedAssets } = useSortedUserAssets(); - const assetsInWallet = sortedAssets as SwappableAsset[]; - const { hiddenCoinsObj } = useCoinListEditOptions(); - - const swappableAssetsRef = useRef( - supportedSwapChainIds.reduce((acc, chainId) => { - acc[chainId] = []; - return acc; - }, {} as SwappableAddresses) - ); - - const filteredAssetsInWallet = (assetsInWallet || []).filter(asset => { - // filter out hidden tokens - if (hiddenCoinsObj[asset.uniqueId]) return true; - - // filter out networks where swaps are not enabled - if (supportedSwapChainIds.includes(asset.chainId)) return true; - - return false; - }); - - const getSwappableAddressesForChainId = useCallback( - async (addresses: EthereumAddress[], chainId: keyof SwappableAddresses) => { - try { - if (outputCurrency) { - const outputChainId = outputCurrency.chainId; - if (outputChainId !== chainId) { - const swappableAddresses = (await walletFilter({ - addresses, - fromChainId: chainId, - toChainId: outputChainId, - })) as string[]; - - swappableAssetsRef.current[chainId] = swappableAddresses; - } else { - swappableAssetsRef.current[chainId] = addresses; - } - } - } catch (e) { - swappableAssetsRef.current[chainId] = addresses; - } - }, - [outputCurrency] - ); - - const getSwappableAddressesInWallet = useCallback(async () => { - const walletFilterRequests: Promise[] = []; - supportedSwapChainIds.forEach(chainId => { - const assetsAddressesOnChain = filteredAssetsInWallet - .filter(asset => (asset?.chainId || ChainId.mainnet) === chainId) - .map(asset => (asset?.address === ETH_ADDRESS ? ETH_ADDRESS_AGGREGATORS : asset?.address)); - if (assetsAddressesOnChain.length) { - walletFilterRequests.push(getSwappableAddressesForChainId(assetsAddressesOnChain, chainId)); - } - }); - await Promise.all(walletFilterRequests); - }, [filteredAssetsInWallet, getSwappableAddressesForChainId]); - - const swappableUserAssets = useMemo( - () => - filteredAssetsInWallet.filter(asset => { - const assetChainId = asset?.chainId || ChainId.mainnet; - const assetAddress = asset?.address === ETH_ADDRESS ? ETH_ADDRESS_AGGREGATORS : asset?.address; - - const isSwappable = swappableAssetsRef.current[assetChainId]?.includes(assetAddress); - return isSwappable; - }), - [filteredAssetsInWallet] - ); - - const unswappableUserAssets = useMemo( - () => - filteredAssetsInWallet.filter(asset => { - const assetChainId = asset?.chainId || ChainId.mainnet; - const assetAddress = asset?.address === ETH_ADDRESS ? ETH_ADDRESS_AGGREGATORS : asset?.address; - const isNotSwappable = !swappableAssetsRef.current[assetChainId]?.includes(assetAddress); - return isNotSwappable; - }), - [filteredAssetsInWallet] - ); - - useEffect(() => { - getSwappableAddressesInWallet(); - }, [getSwappableAddressesInWallet]); - - if (!outputCurrency) { - return { - swappableUserAssets: filteredAssetsInWallet, - unswappableUserAssets: [], - }; - } - - return { - swappableUserAssets, - unswappableUserAssets, - }; -}; diff --git a/src/migrations/migrations/migrateFavorites.ts b/src/migrations/migrations/migrateFavorites.ts index 4ffa4c52259..80915b63322 100644 --- a/src/migrations/migrations/migrateFavorites.ts +++ b/src/migrations/migrations/migrateFavorites.ts @@ -1,5 +1,5 @@ -import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { UniqueId } from '@/__swaps__/types/assets'; +import { getUniqueId } from '@/utils/ethereumUtils'; import { EthereumAddress, RainbowToken } from '@/entities'; import { createQueryKey, persistOptions, queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; @@ -22,10 +22,7 @@ export function migrateFavoritesV2(): Migration { const migratedFavorites: Record = {}; for (const favorite of Object.values(v1Data)) { - const uniqueId = getStandardizedUniqueIdWorklet({ - address: favorite.address as AddressOrEth, - chainId: favorite.chainId, - }); + const uniqueId = getUniqueId(favorite.address, favorite.chainId); favorite.uniqueId = uniqueId; // v2 unique uses chainId instead of Network migratedFavorites[uniqueId] = favorite; } diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index e8828c4db75..47c92298ffc 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -84,7 +84,6 @@ export interface RainbowConfig extends Record remote_promo_enabled: boolean; points_notifications_toggle: boolean; dapp_browser: boolean; - swaps_v2: boolean; idfa_check_enabled: boolean; rewards_enabled: boolean; @@ -170,7 +169,6 @@ export const DEFAULT_CONFIG: RainbowConfig = { remote_promo_enabled: false, points_notifications_toggle: true, dapp_browser: true, - swaps_v2: true, idfa_check_enabled: true, rewards_enabled: true, @@ -229,7 +227,6 @@ export async function fetchRemoteConfig(): Promise { key === 'remote_cards_enabled' || key === 'points_notifications_toggle' || key === 'dapp_browser' || - key === 'swaps_v2' || key === 'idfa_check_enabled' || key === 'rewards_enabled' || key === 'degen_mode' || diff --git a/src/model/wallet.ts b/src/model/wallet.ts index bb4ed3e6df0..1f7d833d014 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -51,7 +51,6 @@ import { Signer } from '@ethersproject/abstract-signer'; import { sanitizeTypedData } from '@/utils/signingUtils'; import { ExecuteFnParamsWithoutFn, performanceTracking, Screen } from '@/state/performance/performance'; import { Network } from '@/chains/types'; -import { WalletBalanceResult } from '@/hooks/useWalletBalances'; export type EthereumPrivateKey = string; type EthereumMnemonic = string; diff --git a/src/navigation/ExchangeModalNavigator.js b/src/navigation/ExchangeModalNavigator.js deleted file mode 100644 index 169702cf191..00000000000 --- a/src/navigation/ExchangeModalNavigator.js +++ /dev/null @@ -1,98 +0,0 @@ -import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; -import { useRoute } from '@react-navigation/native'; -import { createStackNavigator } from '@react-navigation/stack'; -import React, { useCallback, useEffect, useRef } from 'react'; -import { useMemoOne } from 'use-memo-one'; -import { FlexItem } from '../components/layout'; -import { cancelNext, uncancelNext } from '../hooks/useMagicAutofocus'; -import CurrencySelectModal from '../screens/CurrencySelectModal'; -import ExpandedAssetSheet from '../screens/ExpandedAssetSheet'; -import SwapModalScreen from '../screens/SwapModal'; -import { getActiveRoute, useNavigation } from './Navigation'; -import { exchangeTabNavigatorConfig, stackNavigationConfig } from './config'; -import { exchangeModalPreset, expandedPreset, swapSettingsPreset } from './effects'; -import Routes from './routesNames'; -import { useSwapCurrencies } from '@/hooks'; -import styled from '@/styled-thing'; -import { position } from '@/styles'; - -const Stack = createStackNavigator(); -const Tabs = createMaterialTopTabNavigator(); - -const GestureBlocker = styled.View.attrs({ - pointerEvents: 'none', -})({ - ...position.sizeAsObject('100%'), - backgroundColor: ({ theme: { colors } }) => colors.transparent, - position: 'absolute', -}); - -export function ExchangeNavigatorFactory(SwapModal = SwapModalScreen) { - function MainExchangeNavigator() { - const { params } = useRoute(); - - return ( - - - {android && ( - <> - - - - )} - - ); - } - - return function ExchangeModalNavigator() { - const ref = useRef(); - const { params } = useRoute(); - const { setOptions, addListener, removeListener } = useNavigation(); - - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - - useEffect(() => { - // Workaround to fix weird keyboard focus issues upon immediate screen focus then unfocus - if (android && params.fromDiscover) { - addListener('gestureStart', cancelNext); - addListener('blur', uncancelNext); - addListener('focus', uncancelNext); - return () => { - removeListener('gestureStart', cancelNext); - removeListener('blur', uncancelNext); - removeListener('focus', uncancelNext); - }; - } - }, [addListener, removeListener, params]); - - const toggleGestureEnabled = useCallback( - dismissable => { - setOptions({ dismissable, gestureEnabled: dismissable }); - }, - [setOptions] - ); - - const initialParams = useMemoOne( - () => ({ - toggleGestureEnabled, - ...params?.params, - }), - [toggleGestureEnabled] - ); - - const routeName = getActiveRoute()?.name; - - const enableSwipe = routeName === Routes.CURRENCY_SELECT_SCREEN && (!!inputCurrency || !!outputCurrency); - - return ( - - - - - - {ios && } - - ); - }; -} -export default ExchangeNavigatorFactory(); diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index e66ca433ef5..79bfbd90c29 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -60,9 +60,8 @@ import { import { InitialRouteContext } from './initialRoute'; import { onNavigationStateChange } from './onNavigationStateChange'; import Routes from './routesNames'; -import { ExchangeModalNavigator } from './index'; import { deviceUtils } from '@/utils'; -import useExperimentalFlag, { PROFILES, SWAPS_V2 } from '@/config/experimentalHooks'; +import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import QRScannerScreen from '@/screens/QRScannerScreen'; import { PairHardwareWalletNavigator } from './PairHardwareWalletNavigator'; import LearnWebViewScreen from '@/screens/LearnWebViewScreen'; @@ -87,7 +86,6 @@ import { PointsProfileProvider } from '@/screens/points/contexts/PointsProfileCo import walletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import AppIconUnlockSheet from '@/screens/AppIconUnlockSheet'; import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; -import { useRemoteConfig } from '@/model/remoteConfig'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; import { ClaimClaimablePanel } from '@/screens/claimables/ClaimClaimablePanel'; @@ -115,7 +113,6 @@ function MainNavigator() { cardStyleInterpolator: speedUpAndCancelStyleInterpolator, }} /> - @@ -143,9 +140,7 @@ function MainOuterNavigator() { } function BSNavigator() { - const remoteConfig = useRemoteConfig(); const profilesEnabled = useExperimentalFlag(PROFILES); - const swapsV2Enabled = useExperimentalFlag(SWAPS_V2) || remoteConfig.swaps_v2; return ( @@ -250,7 +245,7 @@ function BSNavigator() { - {swapsV2Enabled && } + ); } diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index 1ea1c9553ae..459561484d3 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -45,7 +45,6 @@ import { explainSheetConfig, externalLinkWarningSheetConfig, mintsSheetConfig, - nativeStackDefaultConfig, nftOffersSheetConfig, nftSingleOfferSheetConfig, pairHardwareWalletNavigatorConfig, @@ -60,7 +59,6 @@ import { settingsSheetConfig, signTransactionSheetConfig, stackNavigationConfig, - swapDetailsSheetConfig, learnWebViewScreenConfig, transactionDetailsConfig, addWalletNavigatorConfig, @@ -78,8 +76,7 @@ import { InitialRouteContext } from './initialRoute'; import { nativeStackConfig } from './nativeStackConfig'; import { onNavigationStateChange } from './onNavigationStateChange'; import Routes from './routesNames'; -import { ExchangeModalNavigator } from './index'; -import useExperimentalFlag, { PROFILES, SWAPS_V2 } from '@/config/experimentalHooks'; +import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import createNativeStackNavigator from '@/react-native-cool-modals/createNativeStackNavigator'; import QRScannerScreen from '@/screens/QRScannerScreen'; import { PairHardwareWalletNavigator } from './PairHardwareWalletNavigator'; @@ -100,7 +97,6 @@ import { ConsoleSheet } from '@/screens/points/ConsoleSheet'; import { PointsProfileProvider } from '@/screens/points/contexts/PointsProfileContext'; import AppIconUnlockSheet from '@/screens/AppIconUnlockSheet'; import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; -import { useRemoteConfig } from '@/model/remoteConfig'; import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; @@ -142,9 +138,7 @@ function MainStack() { } function NativeStackNavigator() { - const remoteConfig = useRemoteConfig(); const profilesEnabled = useExperimentalFlag(PROFILES); - const swapsV2Enabled = useExperimentalFlag(SWAPS_V2) || remoteConfig.swaps_v2; return ( @@ -152,11 +146,6 @@ function NativeStackNavigator() { - @@ -232,8 +221,6 @@ function NativeStackNavigator() { - - - - {swapsV2Enabled && } + ); } diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index fbe583c44b8..4de26c91662 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -599,16 +599,6 @@ export const nativeStackDefaultConfigWithoutStatusBar: CoolModalConfigOptions = }, }; -export const exchangeTabNavigatorConfig = { - initialLayout: deviceUtils.dimensions, - sceneContainerStyle: { - backgroundColor: 'transparent', - }, - swipeDistanceMinimum: 0, - tabBar: () => null, - transparentCard: true, -}; - const BackArrow = styled(Icon).attrs({ color: colors.themedColors?.appleBlue, direction: 'left', diff --git a/src/navigation/effects.tsx b/src/navigation/effects.tsx index d56e11a8662..ba02e4e0f2d 100644 --- a/src/navigation/effects.tsx +++ b/src/navigation/effects.tsx @@ -392,16 +392,6 @@ export const expandedPreset: StackNavigationOptions = { detachPreviousScreen: false, }; -export const swapSettingsPreset: StackNavigationOptions = { - cardOverlayEnabled: true, - cardShadowEnabled: true, - cardStyle: { backgroundColor: 'transparent', overflow: 'visible' }, - cardStyleInterpolator: expandStyleInterpolator(1), - gestureDirection: 'vertical', - gestureResponseDistance, - transitionSpec: { close: closeSpec, open: openSpec }, -}; - export const overlayExpandedPreset: StackNavigationOptions = { cardOverlayEnabled: true, cardShadowEnabled: false, @@ -511,17 +501,6 @@ export const settingsPreset = ({ route }: any) => ({ cardStyleInterpolator: sheetStyleInterpolator(0.7), }); -export const exchangeModalPreset = { - cardStyle: { backgroundColor: 'black' }, - cardStyleInterpolator: () => ({ - overlayStyle: { - backgroundColor: 'black', - }, - }), - gestureEnabled: true, - gestureResponseDistance, -}; - export const swapDetailsPreset = { cardOverlayEnabled: true, cardShadowEnabled: true, diff --git a/src/navigation/index.ts b/src/navigation/index.ts index b00572a6620..0a4a3a63bec 100644 --- a/src/navigation/index.ts +++ b/src/navigation/index.ts @@ -1,4 +1,3 @@ -export { default as ExchangeModalNavigator } from './ExchangeModalNavigator'; export { default as Navigation, useNavigation } from './Navigation'; export { default as SpringConfig } from './SpringConfig'; export { default as useStatusBarManaging } from './useStatusBarManaging'; diff --git a/src/navigation/routesNames.ts b/src/navigation/routesNames.ts index 5449336ef1c..96cc67fb146 100644 --- a/src/navigation/routesNames.ts +++ b/src/navigation/routesNames.ts @@ -17,7 +17,6 @@ const Routes = { CONFIRM_REQUEST: 'ConfirmRequest', CONNECTED_DAPPS: 'ConnectedDapps', CONSOLE_SHEET: 'ConsoleSheet', - CURRENCY_SELECT_SCREEN: 'CurrencySelectScreen', CUSTOM_GAS_SHEET: 'CustomGasSheet', DAPP_BROWSER_SCREEN: 'DappBrowserScreen', DAPP_BROWSER: 'DappBrowser', @@ -29,7 +28,6 @@ const Routes = { ENS_CONFIRM_REGISTER_SHEET: 'ENSConfirmRegisterSheet', ENS_INTRO_SHEET: 'ENSIntroSheet', ENS_SEARCH_SHEET: 'ENSSearchSheet', - EXCHANGE_MODAL: 'ExchangeModal', EXPANDED_ASSET_SCREEN: 'ExpandedAssetScreen', EXPANDED_ASSET_SHEET: 'ExpandedAssetSheet', EXPLAIN_SHEET: 'ExplainSheet', @@ -41,8 +39,6 @@ const Routes = { LEARN_WEB_VIEW_SCREEN: 'LearnWebViewScreen', POAP_SHEET: 'PoapSheet', MINT_SHEET: 'MintSheet', - MAIN_EXCHANGE_NAVIGATOR: 'MainExchangeNavigator', - MAIN_EXCHANGE_SCREEN: 'MainExchangeScreen', MAIN_NATIVE_BOTTOM_SHEET_NAVIGATOR: 'MainNativeBottomSheetNavigation', MAIN_NAVIGATOR: 'MainNavigator', MAIN_NAVIGATOR_WRAPPER: 'MainNavigatorWrapper', @@ -85,8 +81,6 @@ const Routes = { SPEED_UP_AND_CANCEL_SHEET: 'SpeedUpAndCancelSheet', STACK: 'Stack', SWAPS_PROMO_SHEET: 'SwapsPromoSheet', - SWAP_DETAILS_SHEET: 'SwapDetailsSheet', - SWAP_SETTINGS_SHEET: 'SwapSettingsSheet', SWIPE_LAYOUT: 'SwipeLayout', TRANSACTION_DETAILS: 'TransactionDetails', NO_NEED_WC_SHEET: 'NoNeedWCSheet', @@ -112,7 +106,6 @@ const Routes = { export const NATIVE_ROUTES = [ Routes.RECEIVE_MODAL, Routes.SETTINGS_SHEET, - Routes.EXCHANGE_MODAL, Routes.EXPANDED_ASSET_SHEET, Routes.CHANGE_WALLET_SHEET, Routes.MODAL_SCREEN, diff --git a/src/navigation/types.ts b/src/navigation/types.ts index 867997d862c..fc87f7da532 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -68,8 +68,6 @@ export type RootStackParamList = { emptyWallet?: boolean; }; [Routes.PROFILE_SCREEN]: any; - [Routes.SWAP_SETTINGS_SHEET]: any; - [Routes.SWAP_DETAILS_SHEET]: any; [Routes.WELCOME_SCREEN]: any; [Routes.ENS_CONFIRM_REGISTER_SHEET]: any; [Routes.PROFILE_SHEET]: { @@ -97,4 +95,9 @@ export type RootStackParamList = { [Routes.WALLET_CONNECT_REDIRECT_SHEET]: { type: WalletconnectResultType; }; + [Routes.EXPANDED_ASSET_SHEET]: { + longFormHeight: number; + type: 'token' | 'unique_token'; + asset: ParsedAddressAsset | UniqueAsset; + }; }; diff --git a/src/parsers/gas.ts b/src/parsers/gas.ts index 86dbbdeeed1..b4e66df7873 100644 --- a/src/parsers/gas.ts +++ b/src/parsers/gas.ts @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; import zipObject from 'lodash/zipObject'; -import { gasUtils } from '../utils'; +import { gasUtils } from '@/utils'; import { BlocksToConfirmation, GasFeeParam, diff --git a/src/raps/actions/crosschainSwap.ts b/src/raps/actions/crosschainSwap.ts index 40cefcc0f5f..6df8f65cf40 100644 --- a/src/raps/actions/crosschainSwap.ts +++ b/src/raps/actions/crosschainSwap.ts @@ -1,7 +1,7 @@ import { Signer } from '@ethersproject/abstract-signer'; import { CrosschainQuote, fillCrosschainQuote } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; +import { estimateGasWithPadding, getProvider, toHex } from '@/handlers/web3'; import { REFERRER, gasUnits, ReferrerType } from '@/references'; import { ChainId } from '@/chains/types'; @@ -10,7 +10,6 @@ import { addNewTransaction } from '@/state/pendingTransactions'; import { RainbowError, logger } from '@/logger'; import { TransactionGasParams, TransactionLegacyGasParams } from '@/__swaps__/types/gas'; -import { toHex } from '@/__swaps__/utils/hex'; import { ActionProps, RapActionResult } from '../references'; import { CHAIN_IDS_WITH_TRACE_SUPPORT, diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index a2d2821ceca..a2331a9fcd3 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -12,7 +12,7 @@ import { unwrapNativeAsset, wrapNativeAsset, } from '@rainbow-me/swaps'; -import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; +import { estimateGasWithPadding, getProvider, toHex } from '@/handlers/web3'; import { Address } from 'viem'; import { metadataPOSTClient } from '@/graphql'; @@ -24,7 +24,6 @@ import { RainbowError, logger } from '@/logger'; import { gasUnits, REFERRER } from '@/references'; import { TransactionGasParams, TransactionLegacyGasParams } from '@/__swaps__/types/gas'; -import { toHex } from '@/__swaps__/utils/hex'; import { ActionProps, RapActionResult } from '../references'; import { CHAIN_IDS_WITH_TRACE_SUPPORT, diff --git a/src/raps/actions/unlock.ts b/src/raps/actions/unlock.ts index af0bce33217..f12aa0c91b7 100644 --- a/src/raps/actions/unlock.ts +++ b/src/raps/actions/unlock.ts @@ -2,7 +2,7 @@ import { Signer } from '@ethersproject/abstract-signer'; import { MaxUint256 } from '@ethersproject/constants'; import { Contract, PopulatedTransaction } from '@ethersproject/contracts'; import { parseUnits } from '@ethersproject/units'; -import { getProvider } from '@/handlers/web3'; +import { getProvider, toHex } from '@/handlers/web3'; import { Address, erc20Abi, erc721Abi } from 'viem'; import { ChainId } from '@/chains/types'; @@ -17,7 +17,6 @@ import { convertAmountToRawAmount, greaterThan } from '@/helpers/utilities'; import { ActionProps, RapActionResult } from '../references'; import { overrideWithFastSpeedIfNeeded } from './../utils'; -import { toHex } from '@/__swaps__/utils/hex'; import { TokenColors } from '@/graphql/__generated__/metadata'; import { ParsedAsset } from '@/resources/assets/types'; import { chainsName } from '@/chains'; diff --git a/src/redux/gas.ts b/src/redux/gas.ts index c8d1d6eaf52..9ce8fe4aac1 100644 --- a/src/redux/gas.ts +++ b/src/redux/gas.ts @@ -42,7 +42,7 @@ import { chainsNativeAsset, chainsSwapPollingInterval, meteorologySupportedChain import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/entities/gas'; import { addBuffer } from '@/helpers/utilities'; -const { CUSTOM, FAST, NORMAL, SLOW, URGENT, FLASHBOTS_MIN_TIP } = gasUtils; +const { CUSTOM, SLOW, NORMAL, FAST, URGENT, FLASHBOTS_MIN_TIP } = gasUtils; const mutex = new Mutex(); diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index b60f32b3042..e5afce994f6 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -12,7 +12,6 @@ import imageMetadata from './imageMetadata'; import keyboardHeight from './keyboardHeight'; import settings from './settings'; import showcaseTokens from './showcaseTokens'; -import swap from './swap'; import transactionSignatures from './transactionSignatures'; import wallets from './wallets'; @@ -28,7 +27,6 @@ export default combineReducers({ keyboardHeight, settings, showcaseTokens, - swap, transactionSignatures, wallets, }); diff --git a/src/redux/swap.ts b/src/redux/swap.ts deleted file mode 100644 index 0ca1da8addd..00000000000 --- a/src/redux/swap.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; -import { AnyAction } from 'redux'; -import { SwappableAsset } from '@/entities'; -import { ExchangeModalTypes } from '@/helpers'; -import { AppDispatch, AppGetState } from '@/redux/store'; -import { Source } from '@/raps/references'; - -export interface SwapAmount { - display: string | null; - value: string | null; -} - -export enum SwapModalField { - input = 'inputAmount', - native = 'nativeAmount', - output = 'outputAmount', -} - -export interface TypeSpecificParameters { - cTokenBalance: string; - supplyBalanceUnderlying: string; -} - -interface SwapState { - derivedValues: any; - displayValues: any; - quoteError: QuoteError | null; - inputCurrency: SwappableAsset | null; - independentField: SwapModalField; - independentValue: string | null; - maxInputUpdate: boolean; - flipCurrenciesUpdate: boolean; - slippageInBips: number; - source: Source; - type: string; - tradeDetails: Quote | CrosschainQuote | null; - typeSpecificParameters?: TypeSpecificParameters | null; - outputCurrency: SwappableAsset | null; -} - -// -- Constants --------------------------------------- // -const SWAP_UPDATE_SLIPPAGE = 'swap/SWAP_UPDATE_SLIPPAGE'; -const SWAP_UPDATE_SOURCE = 'swap/SWAP_UPDATE_SOURCE'; -const SWAP_UPDATE_INPUT_AMOUNT = 'swap/SWAP_UPDATE_INPUT_AMOUNT'; -const SWAP_UPDATE_NATIVE_AMOUNT = 'swap/SWAP_UPDATE_NATIVE_AMOUNT'; -const SWAP_UPDATE_OUTPUT_AMOUNT = 'swap/SWAP_UPDATE_OUTPUT_AMOUNT'; -const SWAP_UPDATE_INPUT_CURRENCY = 'swap/SWAP_UPDATE_INPUT_CURRENCY'; -const SWAP_UPDATE_OUTPUT_CURRENCY = 'swap/SWAP_UPDATE_OUTPUT_CURRENCY'; -const SWAP_FLIP_CURRENCIES = 'swap/SWAP_FLIP_CURRENCIES'; -const SWAP_UPDATE_TYPE_DETAILS = 'swap/SWAP_UPDATE_TYPE_DETAILS'; -const SWAP_UPDATE_QUOTE = 'swap/SWAP_UPDATE_QUOTE'; -const SWAP_CLEAR_STATE = 'swap/SWAP_CLEAR_STATE'; - -// -- Actions ---------------------------------------- // -export const updateSwapTypeDetails = (type: string, typeSpecificParameters?: TypeSpecificParameters | null) => (dispatch: AppDispatch) => { - dispatch({ - payload: { - type, - typeSpecificParameters, - }, - type: SWAP_UPDATE_TYPE_DETAILS, - }); -}; - -export const updateSwapSlippage = (slippage: number) => (dispatch: AppDispatch) => { - dispatch({ - payload: slippage, - type: SWAP_UPDATE_SLIPPAGE, - }); -}; - -export const updateSwapSource = (newSource: Source) => (dispatch: AppDispatch) => { - dispatch({ - payload: newSource, - type: SWAP_UPDATE_SOURCE, - }); -}; - -export const updateSwapInputAmount = - (value: string | null, maxInputUpdate = false) => - (dispatch: AppDispatch) => { - dispatch({ - payload: { independentValue: value, maxInputUpdate }, - type: SWAP_UPDATE_INPUT_AMOUNT, - }); - }; - -export const updateSwapNativeAmount = (value: string | null) => (dispatch: AppDispatch) => { - dispatch({ - payload: value, - type: SWAP_UPDATE_NATIVE_AMOUNT, - }); -}; - -export const updateSwapOutputAmount = (value: string | null) => (dispatch: AppDispatch) => { - dispatch({ - payload: value, - type: SWAP_UPDATE_OUTPUT_AMOUNT, - }); -}; - -export const updateSwapInputCurrency = - (newInputCurrency: SwappableAsset | null, ignoreTypeCheck = false) => - (dispatch: AppDispatch, getState: AppGetState) => { - const { independentField, outputCurrency, type } = getState().swap; - if (type === ExchangeModalTypes.swap && newInputCurrency?.uniqueId === outputCurrency?.uniqueId && newInputCurrency) { - dispatch(flipSwapCurrencies(false)); - } else { - dispatch({ payload: newInputCurrency, type: SWAP_UPDATE_INPUT_CURRENCY }); - if ( - type === ExchangeModalTypes.swap && - newInputCurrency?.chainId !== outputCurrency?.chainId && - newInputCurrency && - !ignoreTypeCheck - ) { - dispatch(updateSwapOutputCurrency(null, true)); - } - - if (independentField === SwapModalField.input) { - dispatch(updateSwapInputAmount(null)); - } - } - }; - -export const updateSwapOutputCurrency = - (newOutputCurrency: SwappableAsset | null, ignoreTypeCheck = false) => - (dispatch: AppDispatch, getState: AppGetState) => { - const { independentField, inputCurrency, type } = getState().swap; - if (newOutputCurrency?.uniqueId === inputCurrency?.uniqueId && newOutputCurrency) { - dispatch(flipSwapCurrencies(true)); - } else { - if ( - type === ExchangeModalTypes.swap && - newOutputCurrency?.chainId !== inputCurrency?.chainId && - newOutputCurrency && - !ignoreTypeCheck - ) { - dispatch(updateSwapInputCurrency(null, true)); - } - - dispatch({ payload: newOutputCurrency, type: SWAP_UPDATE_OUTPUT_CURRENCY }); - if (independentField === SwapModalField.output || newOutputCurrency === null) { - dispatch(updateSwapOutputAmount(null)); - } - } - }; - -export const flipSwapCurrencies = - (outputIndependentField: boolean, independentValue?: string | null) => (dispatch: AppDispatch, getState: AppGetState) => { - const { inputCurrency, outputCurrency } = getState().swap; - dispatch({ - payload: { - flipCurrenciesUpdate: true, - independentField: outputIndependentField ? SwapModalField.output : SwapModalField.input, - independentValue, - newInputCurrency: outputCurrency, - newOutputCurrency: inputCurrency, - }, - type: SWAP_FLIP_CURRENCIES, - }); - }; - -export const updateSwapQuote = (value: any) => (dispatch: AppDispatch) => { - dispatch({ - payload: value, - type: SWAP_UPDATE_QUOTE, - }); -}; - -export const swapClearState = () => (dispatch: AppDispatch) => { - dispatch({ type: SWAP_CLEAR_STATE }); -}; - -// -- Reducer ----------------------------------------- // -const INITIAL_STATE: SwapState = { - derivedValues: null, - displayValues: null, - flipCurrenciesUpdate: false, - independentField: SwapModalField.input, - independentValue: null, - inputCurrency: null, - maxInputUpdate: false, - outputCurrency: null, - quoteError: null, - slippageInBips: 100, - source: Source.AggregatorRainbow, - tradeDetails: null, - type: ExchangeModalTypes.swap, - typeSpecificParameters: null, -}; - -export default (state = INITIAL_STATE, action: AnyAction) => { - switch (action.type) { - case SWAP_UPDATE_QUOTE: - return { - ...state, - derivedValues: action.payload.derivedValues, - displayValues: action.payload.displayValues, - quoteError: action.payload.quoteError, - tradeDetails: action.payload.tradeDetails, - }; - case SWAP_UPDATE_TYPE_DETAILS: - return { - ...state, - type: action.payload.type, - typeSpecificParameters: action.payload.typeSpecificParameters, - }; - case SWAP_UPDATE_SLIPPAGE: - return { - ...state, - slippageInBips: action.payload, - }; - case SWAP_UPDATE_SOURCE: - return { - ...state, - source: action.payload, - }; - case SWAP_UPDATE_INPUT_AMOUNT: - return { - ...state, - flipCurrenciesUpdate: false, - independentField: SwapModalField.input, - independentValue: action.payload.independentValue, - maxInputUpdate: action.payload.maxInputUpdate, - }; - case SWAP_UPDATE_NATIVE_AMOUNT: - return { - ...state, - flipCurrenciesUpdate: false, - independentField: SwapModalField.native, - independentValue: action.payload, - maxInputUpdate: false, - }; - case SWAP_UPDATE_OUTPUT_AMOUNT: - return { - ...state, - flipCurrenciesUpdate: false, - independentField: SwapModalField.output, - independentValue: action.payload, - maxInputUpdate: false, - }; - case SWAP_UPDATE_OUTPUT_CURRENCY: - return { - ...state, - outputCurrency: action.payload, - }; - case SWAP_UPDATE_INPUT_CURRENCY: - return { - ...state, - inputCurrency: action.payload, - }; - case SWAP_FLIP_CURRENCIES: - return { - ...state, - flipCurrenciesUpdate: action.payload.flipCurrenciesUpdate, - independentField: action.payload.independentField, - independentValue: action.payload.independentValue ?? state.independentValue, - inputCurrency: action.payload.newInputCurrency, - maxInputUpdate: false, - outputCurrency: action.payload.newOutputCurrency, - }; - case SWAP_CLEAR_STATE: - return { - ...state, - ...INITIAL_STATE, - }; - default: - return state; - } -}; diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts index b6c6b761cf7..6f32b12b638 100644 --- a/src/resources/assets/assets.ts +++ b/src/resources/assets/assets.ts @@ -12,8 +12,8 @@ import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; import { RainbowPositions } from '@/resources/defi/types'; import { AddysAddressAsset, AddysAsset, ParsedAsset, RainbowAddressAssets } from './types'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/chains/types'; import { chainsIdByName } from '@/chains'; +import { ChainId } from '@/chains/types'; const storage = new MMKV(); diff --git a/src/resources/assets/externalAssetsQuery.ts b/src/resources/assets/externalAssetsQuery.ts index 3baafbcb0fc..03ef2f5e1cf 100644 --- a/src/resources/assets/externalAssetsQuery.ts +++ b/src/resources/assets/externalAssetsQuery.ts @@ -5,7 +5,7 @@ import { convertAmountAndPriceToNativeDisplay, convertAmountToPercentageDisplay import { NativeCurrencyKey } from '@/entities'; import { Token } from '@/graphql/__generated__/metadata'; import { ChainId } from '@/chains/types'; -import { isNativeAsset } from '@/__swaps__/utils/chains'; +import { isNativeAsset } from '@/handlers/assets'; import { AddressOrEth } from '@/__swaps__/types/assets'; export const EXTERNAL_TOKEN_CACHE_TIME = 1000 * 60 * 60 * 24; // 24 hours diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index 82413ed4ac7..f27044c7f5c 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -1,6 +1,7 @@ import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; import { ChainId, Network } from '@/chains/types'; -import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { getUniqueId } from '@/utils/ethereumUtils'; +import { chainsIdByName, chainsName } from '@/chains'; import { NativeCurrencyKeys, RainbowToken } from '@/entities'; import { createQueryKey, queryClient } from '@/react-query'; import { DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; @@ -8,15 +9,12 @@ import { promiseUtils } from '@/utils'; import { useQuery } from '@tanstack/react-query'; import { omit } from 'lodash'; import { externalTokenQueryKey, fetchExternalToken } from './assets/externalAssetsQuery'; -import { chainsIdByName, chainsName } from '@/chains'; import { analyticsV2 } from '@/analytics'; export const favoritesQueryKey = createQueryKey('favorites', {}, { persisterVersion: 4 }); const DEFAULT_FAVORITES = [DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS]; -const getUniqueId = (address: AddressOrEth, chainId: ChainId) => getStandardizedUniqueIdWorklet({ address, chainId }); - /** * Returns a map of the given `addresses` to their corresponding `RainbowToken` metadata. */ diff --git a/src/__swaps__/safe-math/SafeMath.ts b/src/safe-math/SafeMath.ts similarity index 100% rename from src/__swaps__/safe-math/SafeMath.ts rename to src/safe-math/SafeMath.ts diff --git a/src/__swaps__/safe-math/__tests__/SafeMath.test.ts b/src/safe-math/__tests__/SafeMath.test.ts similarity index 100% rename from src/__swaps__/safe-math/__tests__/SafeMath.test.ts rename to src/safe-math/__tests__/SafeMath.test.ts diff --git a/src/screens/CurrencySelectModal.tsx b/src/screens/CurrencySelectModal.tsx deleted file mode 100644 index bca7126d7b0..00000000000 --- a/src/screens/CurrencySelectModal.tsx +++ /dev/null @@ -1,507 +0,0 @@ -import lang from 'i18n-js'; -import { RouteProp, useIsFocused, useRoute } from '@react-navigation/native'; -import { uniqBy } from 'lodash'; -import { matchSorter } from 'match-sorter'; -import React, { Fragment, ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { DefaultSectionT, InteractionManager, Keyboard, Linking, SectionList, TextInput } from 'react-native'; -import { MMKV } from 'react-native-mmkv'; -import Animated from 'react-native-reanimated'; -import { useDebounce } from 'use-debounce'; -import GestureBlocker from '../components/GestureBlocker'; -import { CurrencySelectionList, CurrencySelectModalHeader, ExchangeSearch } from '../components/exchange'; -import NetworkSwitcherv1 from '../components/exchange/NetworkSwitcher'; -import { KeyboardFixedOpenLayout } from '../components/layout'; -import { Modal } from '../components/modal'; -import { STORAGE_IDS } from '../model/mmkv'; -import { analytics } from '@/analytics'; -import { addHexPrefix, isL2Chain } from '@/handlers/web3'; -import { CurrencySelectionTypes, TokenSectionTypes } from '@/helpers'; -import { - useAccountSettings, - useInteraction, - useMagicAutofocus, - usePrevious, - useSwapCurrencies, - useSwapCurrencyList, - useSwappableUserAssets, -} from '@/hooks'; -import { delayNext } from '@/hooks/useMagicAutofocus'; -import { getActiveRoute, useNavigation } from '@/navigation/Navigation'; -import Routes from '@/navigation/routesNames'; -import { filterList } from '@/utils'; -import NetworkSwitcherv2 from '@/components/exchange/NetworkSwitcherv2'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { SwappableAsset } from '@/entities'; -import { Box, Row, Rows } from '@/design-system'; -import { useTheme } from '@/theme'; -import { IS_TEST } from '@/env'; -import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; -import DiscoverSearchInput from '@/components/discover/DiscoverSearchInput'; -import { externalTokenQueryKey, fetchExternalToken } from '@/resources/assets/externalAssetsQuery'; -import { queryClient } from '@/react-query/queryClient'; -import { ChainId, Network } from '@/chains/types'; -import { chainsName } from '@/chains'; - -export interface EnrichedExchangeAsset extends SwappableAsset { - ens: boolean; - color: string; - nickname: string; - onPress: (el: ReactElement) => void; - testID: string; - useGradientText: boolean; - title?: string; - key: string; - disabled?: boolean; -} - -const storage = new MMKV(); -const getHasShownWarning = () => storage.getBoolean(STORAGE_IDS.SHOWN_SWAP_RESET_WARNING); -const setHasShownWarning = () => storage.set(STORAGE_IDS.SHOWN_SWAP_RESET_WARNING, true); - -const headerlessSection = (data: SwappableAsset[]): { data: SwappableAsset[]; title: string; key: string }[] => [ - { data, title: '', key: 'swappableAssets' }, -]; -const Wrapper = ios ? KeyboardFixedOpenLayout : Fragment; - -const searchWalletCurrencyList = (searchList: SwappableAsset[], query: string) => { - const isAddress = query.match(/^(0x)?[0-9a-fA-F]{40}$/); - - if (isAddress) { - const formattedQuery = addHexPrefix(query).toLowerCase(); - return filterList(searchList, formattedQuery, ['address'], { - threshold: matchSorter.rankings.CASE_SENSITIVE_EQUAL, - }); - } - - return filterList(searchList, query, ['symbol', 'name'], { - threshold: matchSorter.rankings.CONTAINS, - }); -}; - -type ParamList = { - Currency: { - defaultOutputAsset: SwappableAsset; - defaultInputAsset: SwappableAsset; - chainId: number; - fromDiscover: boolean; - onSelectCurrency: (asset: SwappableAsset, cb: (item: any) => void) => void; - params: Record; - restoreFocusOnSwapModal: () => void; - toggleGestureEnabled: (arg: boolean) => void; - type: string; - callback: () => void; - }; -}; - -export default function CurrencySelectModal() { - const isFocused = useIsFocused(); - const prevIsFocused = usePrevious(isFocused); - const { goBack, navigate, getState: dangerouslyGetState } = useNavigation(); - const { nativeCurrency } = useAccountSettings(); - const { colors } = useTheme(); - const { - params: { - defaultOutputAsset, - defaultInputAsset, - chainId, - fromDiscover, - onSelectCurrency, - params, - restoreFocusOnSwapModal, - toggleGestureEnabled, - type, - callback, - }, - } = useRoute>(); - - const listRef = useRef>(null); - - const searchInputRef = useRef(null); - const { handleFocus } = useMagicAutofocus(searchInputRef, undefined, true); - - const [searchQuery, setSearchQuery] = useState(''); - const [searchQueryForSearch] = useDebounce(searchQuery, 350); - const { data: sortedAssets } = useSortedUserAssets(); - const assetsInWallet = sortedAssets as SwappableAsset[]; - - const [currentChainId, setCurrentChainId] = useState(chainId); - const prevChainId = usePrevious(currentChainId); - - const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - const NetworkSwitcher = crosschainSwapsEnabled ? NetworkSwitcherv2 : NetworkSwitcherv1; - const SearchInput = crosschainSwapsEnabled ? DiscoverSearchInput : ExchangeSearch; - - useEffect(() => { - if (chainId && typeof chainId === 'number') { - setCurrentChainId(chainId); - } - }, [chainId]); - - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - - const { crosschainExactMatches, swapCurrencyList, swapCurrencyListLoading } = useSwapCurrencyList( - searchQueryForSearch, - currentChainId, - false - ); - - const { swappableUserAssets, unswappableUserAssets } = useSwappableUserAssets({ outputCurrency }); - - const checkForSameNetwork = useCallback( - (newAsset: any, selectAsset: any, type: any) => { - const otherAsset = type === 'input' ? outputCurrency : inputCurrency; - const hasShownWarning = getHasShownWarning(); - if (otherAsset && newAsset?.chainId !== otherAsset?.chainId && !hasShownWarning) { - Keyboard.dismiss(); - InteractionManager.runAfterInteractions(() => { - navigate(Routes.EXPLAIN_SHEET, { - chainId: newAsset?.chainId, - onClose: () => { - setHasShownWarning(); - selectAsset(); - }, - type: 'swapResetInputs', - }); - }); - - return true; - } - return false; - }, - [inputCurrency, navigate, outputCurrency] - ); - const [isTransitioning, setIsTransitioning] = useState(false); - const routeName = getActiveRoute()?.name; - const showList = useMemo(() => { - const viewingExplainer = routeName === Routes.EXPLAIN_SHEET; - return isFocused || viewingExplainer || isTransitioning; - }, [isFocused, routeName, isTransitioning]); - - const linkToHop = useCallback(() => { - Linking.openURL('https://app.hop.exchange/#/send'); - }, []); - - const getWalletCurrencyList = useCallback(() => { - let walletCurrencyList; - if (type === CurrencySelectionTypes.input) { - if (searchQueryForSearch !== '') { - const searchResults = searchWalletCurrencyList(swappableUserAssets, searchQueryForSearch); - walletCurrencyList = headerlessSection(searchResults); - if (crosschainSwapsEnabled) { - const unswappableSearchResults = searchWalletCurrencyList(unswappableUserAssets, searchQueryForSearch); - walletCurrencyList.push({ - data: unswappableSearchResults.map(unswappableAsset => ({ - ...unswappableAsset, - disabled: true, - })), - title: lang.t(`exchange.token_sections.${TokenSectionTypes.unswappableTokenSection}`), - key: 'unswappableAssets', - }); - } - return walletCurrencyList; - } else { - walletCurrencyList = headerlessSection(swappableUserAssets); - let unswappableAssets = unswappableUserAssets; - if (IS_TEST) { - unswappableAssets = unswappableAssets.concat({ - address: '0x123', - decimals: 18, - name: 'Unswappable', - symbol: 'UNSWAP', - network: Network.mainnet, - chainId: ChainId.mainnet, - id: 'foobar', - uniqueId: '0x123', - }); - } - if (crosschainSwapsEnabled) { - walletCurrencyList.push({ - data: unswappableAssets.map(unswappableAsset => ({ - ...unswappableAsset, - disabled: true, - })), - title: lang.t(`exchange.token_sections.${TokenSectionTypes.unswappableTokenSection}`), - key: 'unswappableAssets', - }); - } - return walletCurrencyList; - } - } - }, [searchQueryForSearch, type, crosschainSwapsEnabled, swappableUserAssets, unswappableUserAssets]); - - const activeSwapCurrencyList = useMemo(() => { - if (crosschainExactMatches.length) { - return crosschainExactMatches; - } - return swapCurrencyList; - }, [crosschainExactMatches, swapCurrencyList]); - - const currencyList = useMemo(() => { - let list = (type === CurrencySelectionTypes.input ? getWalletCurrencyList() : activeSwapCurrencyList) as { - data: EnrichedExchangeAsset[]; - title: string; - }[]; - - // Remove tokens that show up in two lists and empty sections - let uniqueIds: string[] = []; - list = list?.map(section => { - // Remove dupes - section.data = uniqBy(section?.data, 'uniqueId'); - // Remove dupes across sections - section.data = section?.data?.filter(token => !uniqueIds.includes(token?.uniqueId)); - const sectionUniqueIds = section?.data?.map(token => token?.uniqueId); - uniqueIds = uniqueIds.concat(sectionUniqueIds); - - return section; - }); - - // ONLY FOR e2e!!! Fake tokens with same symbols break detox e2e tests - if (IS_TEST && type === CurrencySelectionTypes.output) { - let symbols: string[] = []; - list = list?.map(section => { - // Remove dupes - section.data = uniqBy(section?.data, 'symbol'); - // Remove dupes across sections - section.data = section?.data?.filter(token => !symbols.includes(token?.symbol)); - const sectionSymbols = section?.data?.map(token => token?.symbol); - symbols = symbols.concat(sectionSymbols); - - return section; - }); - } - return list.filter(section => section.data.length > 0); - }, [activeSwapCurrencyList, getWalletCurrencyList, type]); - - const handleNavigate = useCallback( - (item: any) => { - delayNext(); - // @ts-expect-error read only property - dangerouslyGetState().index = 1; - if (fromDiscover) { - goBack(); - setTimeout( - () => { - navigate(Routes.EXCHANGE_MODAL, { - params: { - inputAsset: type === CurrencySelectionTypes.output ? defaultInputAsset : item, - outputAsset: type === CurrencySelectionTypes.input ? defaultOutputAsset : item, - ...params, - }, - screen: Routes.MAIN_EXCHANGE_SCREEN, - }); - setSearchQuery(''); - setCurrentChainId(item.chainId); - }, - android ? 500 : 0 - ); - } else { - navigate(Routes.MAIN_EXCHANGE_SCREEN); - setSearchQuery(''); - setCurrentChainId(item.chainId); - } - if (searchQueryForSearch) { - analytics.track('Selected a search result in Swap', { - name: item.name, - searchQueryForSearch, - symbol: item.symbol, - tokenAddress: item.address, - type, - }); - } - }, - [dangerouslyGetState, defaultInputAsset, defaultOutputAsset, fromDiscover, goBack, navigate, params, searchQueryForSearch, type] - ); - const checkForRequiredAssets = useCallback( - (item: any) => { - if (type === CurrencySelectionTypes.output && currentChainId && currentChainId !== ChainId.mainnet) { - const currentL2WalletAssets = assetsInWallet.filter( - ({ network }) => network && network?.toLowerCase() === chainsName[currentChainId]?.toLowerCase() - ); - if (currentL2WalletAssets?.length < 1) { - Keyboard.dismiss(); - InteractionManager.runAfterInteractions(() => { - navigate(Routes.EXPLAIN_SHEET, { - assetName: item?.symbol, - chainId: currentChainId, - onClose: linkToHop, - type: 'obtainL2Assets', - }); - }); - return true; - } - return false; - } - }, - [assetsInWallet, currentChainId, linkToHop, navigate, type] - ); - - const handleSelectAsset = useCallback( - (item: any) => { - if (!crosschainSwapsEnabled && checkForRequiredAssets(item)) return; - - let newAsset = item; - - const selectAsset = async () => { - if (!item?.balance) { - const externalAsset = await queryClient.fetchQuery( - externalTokenQueryKey({ - address: item.address, - chainId: currentChainId, - currency: nativeCurrency, - }), - async () => - fetchExternalToken({ - address: item.address, - chainId: currentChainId, - currency: nativeCurrency, - }), - { - staleTime: Infinity, - } - ); - // if the asset is external we need to add the network specific information - newAsset = { - ...newAsset, - decimals: item?.networks?.[currentChainId]?.decimals || item.decimals, - address: item?.address || item?.networks?.[currentChainId]?.address, - network: chainsName[currentChainId], - ...externalAsset, - }; - } - setIsTransitioning(true); // continue to display list during transition - callback?.(); - onSelectCurrency(newAsset, handleNavigate); - }; - if ( - !crosschainSwapsEnabled && - checkForSameNetwork( - newAsset, - selectAsset, - type === CurrencySelectionTypes.output ? CurrencySelectionTypes.output : CurrencySelectionTypes.input - ) - ) - return; - - selectAsset(); - }, - [ - crosschainSwapsEnabled, - checkForRequiredAssets, - currentChainId, - type, - checkForSameNetwork, - nativeCurrency, - callback, - onSelectCurrency, - handleNavigate, - ] - ); - - const itemProps = useMemo(() => { - return { - onPress: handleSelectAsset, - showBalance: type === CurrencySelectionTypes.input, - showFavoriteButton: type === CurrencySelectionTypes.output && currentChainId === ChainId.mainnet, - }; - }, [handleSelectAsset, type, currentChainId]); - - const searchingOnL2Network = useMemo(() => isL2Chain({ chainId: currentChainId }), [currentChainId]); - - const [startInteraction] = useInteraction(); - useEffect(() => { - if (!fromDiscover) { - if (isFocused !== prevIsFocused) { - toggleGestureEnabled(!isFocused); - } - if (!isFocused && prevIsFocused) { - restoreFocusOnSwapModal?.(); - setTimeout(() => { - setIsTransitioning(false); // hide list now that we have arrived on main exchange modal - }, 750); - } - } - }, [isFocused, startInteraction, prevIsFocused, restoreFocusOnSwapModal, toggleGestureEnabled, fromDiscover]); - - const handleBackButton = useCallback(() => { - setSearchQuery(''); - InteractionManager.runAfterInteractions(() => { - setCurrentChainId(inputCurrency?.chainId); - }); - setIsTransitioning(true); // continue to display list while transitiong back - }, [inputCurrency?.chainId]); - - useEffect(() => { - // check if list has items before attempting to scroll - if (!currencyList[0]?.data) return; - if (currentChainId !== prevChainId) { - listRef?.current?.scrollToLocation({ - animated: false, - itemIndex: 0, - sectionIndex: 0, - viewOffset: 0, - viewPosition: 0, - }); - } - }, [currencyList, currentChainId, prevChainId]); - - return ( - - - {/* @ts-expect-error JavaScript component */} - - - - - - - - - - {type === CurrencySelectionTypes.output && ( - - {/* @ts-expect-error JavaScript component */} - - - )} - {type === null || type === undefined ? null : ( - - )} - - - - - - ); -} diff --git a/src/screens/ENSConfirmRegisterSheet.tsx b/src/screens/ENSConfirmRegisterSheet.tsx index 7db4480e300..600d160b929 100644 --- a/src/screens/ENSConfirmRegisterSheet.tsx +++ b/src/screens/ENSConfirmRegisterSheet.tsx @@ -30,7 +30,6 @@ import { useENSRegistrationForm, useENSRegistrationStepHandler, useENSSearch, - useWallets, } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { useNavigation } from '@/navigation'; diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx deleted file mode 100644 index 2f8aa2c28df..00000000000 --- a/src/screens/ExchangeModal.tsx +++ /dev/null @@ -1,916 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import { isEmpty, isEqual } from 'lodash'; -import React, { MutableRefObject, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import equal from 'react-fast-compare'; -import { EmitterSubscription, InteractionManager, Keyboard, NativeModules, TextInput, View } from 'react-native'; -import { useAndroidBackHandler } from 'react-navigation-backhandler'; -import { useDispatch } from 'react-redux'; -import { useDebounce } from 'use-debounce'; -import { useMemoOne } from 'use-memo-one'; -import { dismissingScreenListener } from '../../shim'; -import { - ConfirmExchangeButton, - ExchangeDetailsRow, - ExchangeFloatingPanels, - ExchangeHeader, - ExchangeInputField, - ExchangeNotch, - ExchangeOutputField, -} from '../components/exchange'; -import { FloatingPanel } from '../components/floating-panels'; -import { GasSpeedButton } from '../components/gas'; -import { KeyboardFixedOpenLayout } from '../components/layout'; -import { delayNext } from '../hooks/useMagicAutofocus'; -import { getRemoteConfig, useRemoteConfig } from '@/model/remoteConfig'; -import { WrappedAlert as Alert } from '@/helpers/alert'; -import { analytics } from '@/analytics'; -import { Box, Row, Rows } from '@/design-system'; -import { GasFee, LegacyGasFee, LegacyGasFeeParams, SwappableAsset } from '@/entities'; -import { ExchangeModalTypes, isKeyboardOpen } from '@/helpers'; -import { KeyboardType } from '@/helpers/keyboardTypes'; -import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; -import { delay, greaterThan } from '@/helpers/utilities'; -import { - useAccountSettings, - useColorForAsset, - useGas, - usePrevious, - usePriceImpactDetails, - useSwapCurrencies, - useSwapCurrencyHandlers, - useSwapDerivedOutputs, - useSwapInputHandlers, - useSwapInputRefs, - useSwapIsSufficientBalance, - useSwapSettings, - useWallets, -} from '@/hooks'; -import { loadWallet } from '@/model/wallet'; -import { useNavigation } from '@/navigation'; -import { walletExecuteRap } from '@/raps/execute'; -import { swapClearState, SwapModalField, TypeSpecificParameters, updateSwapSlippage, updateSwapTypeDetails } from '@/redux/swap'; -import { ethUnits } from '@/references'; -import Routes from '@/navigation/routesNames'; -import { ethereumUtils, gasUtils } from '@/utils'; -import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import { logger, RainbowError } from '@/logger'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { CrosschainQuote, Quote, SwapType } from '@rainbow-me/swaps'; -import store from '@/redux/store'; -import { getCrosschainSwapServiceTime } from '@/handlers/swap'; -import useParamsForExchangeModal from '@/hooks/useParamsForExchangeModal'; -import { Wallet } from '@ethersproject/wallet'; -import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; -import { useTheme } from '@/theme'; -import Animated from 'react-native-reanimated'; -import { handleReviewPromptAction } from '@/utils/reviewAlert'; -import { ReviewPromptAction } from '@/storage/schema'; -import { SwapPriceImpactType } from '@/hooks/usePriceImpactDetails'; -import { getNextNonce } from '@/state/nonces'; -import { ChainId } from '@/chains/types'; -import { AddressOrEth, ParsedAsset } from '@/__swaps__/types/assets'; -import { TokenColors } from '@/graphql/__generated__/metadata'; -import { estimateSwapGasLimit } from '@/raps/actions'; -import { estimateCrosschainSwapGasLimit } from '@/raps/actions/crosschainSwap'; -import { parseGasParamAmounts } from '@/parsers'; -import { chainsName, needsL1SecurityFeeChains, shouldDefaultToFastGasChainIds, supportedFlashbotsChainIds } from '@/chains'; - -export const DEFAULT_SLIPPAGE_BIPS = { - [ChainId.mainnet]: 100, - [ChainId.polygon]: 200, - [ChainId.base]: 200, - [ChainId.bsc]: 200, - [ChainId.optimism]: 200, - [ChainId.arbitrum]: 200, - [ChainId.goerli]: 100, - [ChainId.gnosis]: 200, - [ChainId.zora]: 200, - [ChainId.avalanche]: 200, - [ChainId.blast]: 200, - [ChainId.degen]: 200, -}; - -export const getDefaultSlippageFromConfig = (chainId: ChainId) => { - const configSlippage = getRemoteConfig().default_slippage_bips as unknown as { - [network: string]: number; - }; - const network = chainsName[chainId]; - const slippage = configSlippage?.[network] ?? DEFAULT_SLIPPAGE_BIPS[chainId] ?? 100; - return slippage; -}; -const NOOP = () => null; - -const FloatingPanels = Animated.createAnimatedComponent(ExchangeFloatingPanels); - -const Wrapper = KeyboardFixedOpenLayout; - -interface ExchangeModalProps { - fromDiscover: boolean; - ignoreInitialTypeCheck?: boolean; - testID: string; - type: keyof typeof ExchangeModalTypes; - typeSpecificParams: TypeSpecificParameters; -} - -export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, type, typeSpecificParams }: ExchangeModalProps) { - const { isHardwareWallet } = useWallets(); - const dispatch = useDispatch(); - const { slippageInBips, maxInputUpdate, flipCurrenciesUpdate } = useSwapSettings(); - const { - params: { inputAsset: defaultInputAsset, outputAsset: defaultOutputAsset }, - } = useRoute<{ - key: string; - name: string; - params: { inputAsset: SwappableAsset; outputAsset: SwappableAsset }; - }>(); - const { default_slippage_bips } = useRemoteConfig(); - - const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - - useLayoutEffect(() => { - dispatch(updateSwapTypeDetails(type, typeSpecificParams)); - }, [dispatch, type, typeSpecificParams]); - - const title = lang.t('swap.modal_types.swap'); - - const { goBack, navigate, setParams, getParent: dangerouslyGetParent, addListener } = useNavigation(); - - const { - selectedGasFee, - gasFeeParamsBySpeed, - startPollingGasFees, - stopPollingGasFees, - updateDefaultGasLimit, - updateGasFeeOption, - updateTxFee, - chainId, - isGasReady, - } = useGas(); - const { accountAddress, flashbotsEnabled, nativeCurrency } = useAccountSettings(); - - const [isAuthorizing, setIsAuthorizing] = useState(false); - const prevGasFeesParamsBySpeed = usePrevious(gasFeeParamsBySpeed); - const prevChainId = usePrevious(chainId); - - const keyboardListenerSubscription = useRef(); - - useAndroidBackHandler(() => { - navigate(Routes.WALLET_SCREEN); - return true; - }); - - const { inputCurrency, outputCurrency } = useSwapCurrencies(); - - const { colors } = useTheme(); - const inputCurrencyColor = useColorForAsset(inputCurrency, colors.appleBlue); - const outputCurrencyColor = useColorForAsset(outputCurrency, colors.appleBlue); - - const { handleFocus, inputFieldRef, lastFocusedInputHandle, setLastFocusedInputHandle, nativeFieldRef, outputFieldRef } = - useSwapInputRefs(); - - const { updateInputAmount, updateMaxInputAmount, updateNativeAmount, updateOutputAmount } = useSwapInputHandlers(); - - const { inputChainId, outputChainId, currentChainId, isCrosschainSwap, isBridgeSwap } = useMemo(() => { - const inputChainId = inputCurrency?.chainId || ChainId.mainnet; - const outputChainId = outputCurrency?.chainId || ChainId.mainnet; - const chainId: ChainId = inputChainId || outputChainId; - - const isCrosschainSwap = crosschainSwapsEnabled && inputChainId !== outputChainId; - const isBridgeSwap = inputCurrency?.symbol === outputCurrency?.symbol; - return { - inputChainId, - outputChainId, - currentChainId: chainId, - isCrosschainSwap, - isBridgeSwap, - }; - }, [inputCurrency?.chainId, inputCurrency?.symbol, outputCurrency?.chainId, outputCurrency?.symbol, crosschainSwapsEnabled]); - - const { flipCurrencies, navigateToSelectInputCurrency, navigateToSelectOutputCurrency } = useSwapCurrencyHandlers({ - inputChainId, - outputChainId, - defaultInputAsset, - defaultOutputAsset, - fromDiscover, - ignoreInitialTypeCheck, - inputFieldRef, - lastFocusedInputHandle, - nativeFieldRef, - outputFieldRef, - setLastFocusedInputHandle, - title, - type, - }); - const speedUrgentSelected = useRef(false); - - useEffect(() => { - if (!speedUrgentSelected.current && !isEmpty(gasFeeParamsBySpeed) && shouldDefaultToFastGasChainIds.includes(currentChainId)) { - // Default to fast for networks with speed options - updateGasFeeOption(gasUtils.FAST); - speedUrgentSelected.current = true; - } - }, [currentChainId, gasFeeParamsBySpeed, selectedGasFee, updateGasFeeOption, updateTxFee]); - - useEffect(() => { - if (currentChainId !== prevChainId) { - speedUrgentSelected.current = false; - } - }, [currentChainId, prevChainId]); - - const defaultGasLimit = useMemo(() => { - return ethereumUtils.getBasicSwapGasLimit(Number(currentChainId)); - }, [currentChainId]); - - const { - result: { - derivedValues: { inputAmount, outputAmount }, - displayValues: { inputAmountDisplay, outputAmountDisplay, nativeAmountDisplay }, - tradeDetails, - }, - loading, - resetSwapInputs, - quoteError, - } = useSwapDerivedOutputs(type); - - const lastTradeDetails = usePrevious(tradeDetails); - const isSufficientBalance = useSwapIsSufficientBalance(inputAmount); - - const { priceImpact, outputNativeAmount } = usePriceImpactDetails(inputCurrency, outputCurrency, tradeDetails, currentChainId); - const [debouncedIsHighPriceImpact] = useDebounce(priceImpact.type !== SwapPriceImpactType.none, 1000); - // For a limited period after the merge we need to block the use of flashbots. - // This line should be removed after reenabling flashbots in remote config. - const swapSupportsFlashbots = supportedFlashbotsChainIds.includes(currentChainId); - const flashbots = swapSupportsFlashbots && flashbotsEnabled; - - const isDismissing = useRef(false); - useEffect(() => { - if (ios) { - return; - } - (dismissingScreenListener.current as unknown as () => void) = () => { - Keyboard.dismiss(); - isDismissing.current = true; - }; - const unsubscribe = (dangerouslyGetParent()?.getParent()?.addListener || addListener)( - // @ts-expect-error - Not sure if this is even triggered as React Navigation apparently doesnt emit this event. - 'transitionEnd', - // @ts-expect-error - Can't find any docs around this closing prop being sent is this a private API? - ({ data: { closing } }) => { - if (!closing && isDismissing.current) { - isDismissing.current = false; - (lastFocusedInputHandle as unknown as MutableRefObject)?.current?.focus(); - } - } - ); - return () => { - unsubscribe(); - dismissingScreenListener.current = undefined; - }; - }, [addListener, dangerouslyGetParent, lastFocusedInputHandle]); - - useEffect(() => { - const slippage = getDefaultSlippageFromConfig(currentChainId); - slippage && dispatch(updateSwapSlippage(slippage)); - }, [currentChainId, default_slippage_bips, dispatch]); - - useEffect(() => { - return () => { - dispatch(swapClearState()); - resetSwapInputs(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const updateGasLimit = useCallback(async () => { - try { - const provider = getProvider({ chainId: currentChainId }); - - const quote = isCrosschainSwap ? (tradeDetails as CrosschainQuote) : (tradeDetails as Quote); - const gasLimit = await (isCrosschainSwap ? estimateCrosschainSwapGasLimit : estimateSwapGasLimit)({ - chainId: currentChainId, - quote: quote as CrosschainQuote, - }); - - if (gasLimit) { - if (needsL1SecurityFeeChains.includes(currentChainId)) { - if (tradeDetails) { - const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - { - data: tradeDetails.data, - from: tradeDetails.from, - to: tradeDetails.to ?? null, - value: tradeDetails.value, - }, - provider - ); - updateTxFee(gasLimit, null, l1GasFeeOptimism); - } else { - updateTxFee(gasLimit, null, ethUnits.default_l1_gas_fee_optimism_swap); - } - } else { - updateTxFee(gasLimit, null); - } - } - } catch (error) { - updateTxFee(defaultGasLimit, null); - } - }, [currentChainId, defaultGasLimit, isCrosschainSwap, tradeDetails, updateTxFee]); - - useEffect(() => { - if (tradeDetails && !equal(tradeDetails, lastTradeDetails)) { - updateGasLimit(); - } - }, [lastTradeDetails, tradeDetails, updateGasLimit]); - - // Set default gas limit - useEffect(() => { - if (isEmpty(prevGasFeesParamsBySpeed) && !isEmpty(gasFeeParamsBySpeed)) { - updateTxFee(defaultGasLimit, null); - } - }, [defaultGasLimit, gasFeeParamsBySpeed, prevGasFeesParamsBySpeed, updateTxFee]); - - // Update gas limit - useEffect(() => { - if ( - !isGasReady || - (!prevChainId && currentChainId !== prevChainId) || - (!isEmpty(gasFeeParamsBySpeed) && !isEqual(gasFeeParamsBySpeed, prevGasFeesParamsBySpeed)) - ) { - updateGasLimit(); - } - }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, updateGasLimit]); - - // Listen to gas prices, Uniswap reserves updates - useEffect(() => { - updateDefaultGasLimit(defaultGasLimit); - InteractionManager.runAfterInteractions(() => { - // Start polling in the current network - startPollingGasFees(currentChainId, flashbots); - }); - return () => { - stopPollingGasFees(); - }; - }, [defaultGasLimit, currentChainId, startPollingGasFees, stopPollingGasFees, updateDefaultGasLimit, flashbots]); - - const checkGasVsOutput = async (gasPrice: string, outputPrice: string) => { - if (greaterThan(outputPrice, 0) && greaterThan(gasPrice, outputPrice) && !(IS_ANDROID && IS_TEST)) { - const res = new Promise(resolve => { - Alert.alert(lang.t('swap.warning.cost.are_you_sure_title'), lang.t('swap.warning.cost.this_transaction_will_cost_you_more'), [ - { - onPress: () => { - resolve(false); - }, - text: lang.t('button.proceed_anyway'), - }, - { - onPress: () => { - resolve(true); - }, - style: 'cancel', - text: lang.t('button.cancel'), - }, - ]); - }); - return res; - } else { - return false; - } - }; - - const isFillingParams = useParamsForExchangeModal({ - inputFieldRef, - outputFieldRef, - nativeFieldRef, - }); - - const submit = useCallback( - async (amountInUSD: string): Promise => { - setIsAuthorizing(true); - const NotificationManager = ios ? NativeModules.NotificationManager : null; - try { - // load the correct network provider for the wallet - const provider = getProvider({ chainId: currentChainId }); - let wallet = await loadWallet({ - address: accountAddress, - showErrorIfNotLoaded: false, - provider, - }); - if (!wallet) { - setIsAuthorizing(false); - logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing wallet`)); - Alert.alert('Unable to determine wallet address'); - return false; - } - - // Switch to the flashbots provider if enabled - // TODO(skylarbarrera): need to check if ledger and handle differently here - if (flashbots && supportedFlashbotsChainIds.includes(currentChainId) && wallet instanceof Wallet) { - logger.debug('[ExchangeModal]: flashbots provider being set on mainnet'); - const flashbotsProvider = await getFlashbotsProvider(); - wallet = new Wallet(wallet.privateKey, flashbotsProvider); - } - - if (!inputAmount || !outputAmount) { - logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing inputAmount or outputAmount`)); - Alert.alert('Input amount or output amount is missing'); - return false; - } - - if (!tradeDetails) { - logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing tradeDetails`)); - Alert.alert('Missing trade details for swap'); - return false; - } - - logger.debug(`[ExchangeModal]: getting nonce for account ${accountAddress}`); - const currentNonce = await getNextNonce({ address: accountAddress, chainId: currentChainId }); - logger.debug(`[ExchangeModal]: nonce for account ${accountAddress} is ${currentNonce}`); - const { independentField, independentValue, slippageInBips, source } = store.getState().swap; - - const transformedAssetToSell = { - ...inputCurrency, - chainName: chainsName[inputCurrency.chainId], - address: inputCurrency.address as AddressOrEth, - chainId: inputCurrency.chainId, - colors: inputCurrency.colors as TokenColors, - } as ParsedAsset; - - const transformedAssetToBuy = { - ...outputCurrency, - chainName: chainsName[outputCurrency.chainId], - address: outputCurrency.address as AddressOrEth, - chainId: outputCurrency.chainId, - colors: outputCurrency.colors as TokenColors, - } as ParsedAsset; - - const isWrapOrUnwrapEth = () => { - return tradeDetails.swapType === SwapType.wrap || tradeDetails.swapType === SwapType.unwrap; - }; - - const { errorMessage } = await walletExecuteRap(wallet, isCrosschainSwap ? 'crosschainSwap' : 'swap', { - chainId: currentChainId, - flashbots, - nonce: currentNonce, - assetToSell: transformedAssetToSell, - assetToBuy: transformedAssetToBuy, - sellAmount: inputAmount, - quote: { - ...tradeDetails, - feeInEth: isWrapOrUnwrapEth() ? '0' : tradeDetails.feeInEth, - }, - amount: inputAmount, - meta: { - inputAsset: transformedAssetToSell, - outputAsset: transformedAssetToBuy, - independentField: independentField as SwapModalField, - independentValue: independentValue as string, - slippage: slippageInBips, - route: source, - }, - gasParams: parseGasParamAmounts(selectedGasFee), - gasFeeParamsBySpeed, - }); - - setIsAuthorizing(false); - // if the transaction was not successful, we need to bubble that up to the caller - if (errorMessage) { - logger.error(new RainbowError(`[ExchangeModal]: transaction was not successful: ${errorMessage}`)); - if (wallet instanceof Wallet) { - Alert.alert(errorMessage); - } else { - setHardwareTXError(true); - } - return false; - } - - logger.debug('[ExchangeModal]: executed rap!'); - const slippage = slippageInBips / 100; - analytics.track(`Completed ${type}`, { - aggregator: tradeDetails?.source || '', - amountInUSD, - gasSetting: selectedGasFee?.option, - inputTokenAddress: inputCurrency?.address || '', - inputTokenName: inputCurrency?.name || '', - inputTokenSymbol: inputCurrency?.symbol || '', - isHardwareWallet, - isHighPriceImpact: debouncedIsHighPriceImpact, - legacyGasPrice: (selectedGasFee?.gasFeeParams as unknown as LegacyGasFeeParams)?.gasPrice?.amount || '', - liquiditySources: JSON.stringify(tradeDetails?.protocols || []), - maxNetworkFee: (selectedGasFee?.gasFee as GasFee)?.maxFee?.value?.amount || '', - network: chainsName[currentChainId], - networkFee: selectedGasFee?.gasFee?.estimatedFee?.value?.amount || '', - outputTokenAddress: outputCurrency?.address || '', - outputTokenName: outputCurrency?.name || '', - outputTokenSymbol: outputCurrency?.symbol || '', - priceImpact: priceImpact.percentDisplay, - slippage: isNaN(slippage) ? 'Error calculating slippage.' : slippage, - type, - }); - // Tell iOS we finished running a rap (for tracking purposes) - NotificationManager?.postNotification('rapCompleted'); - - setTimeout(() => { - if (isBridgeSwap) { - handleReviewPromptAction(ReviewPromptAction.BridgeToL2); - } else { - handleReviewPromptAction(ReviewPromptAction.Swap); - } - }, 500); - - setParams({ focused: false }); - navigate(Routes.PROFILE_SCREEN); - - return true; - } catch (error) { - setIsAuthorizing(false); - logger.error(new RainbowError(`[ExchangeModal]: error submitting swap: ${error}`)); - setParams({ focused: false }); - // close the hardware wallet modal before navigating - if (isHardwareWallet) { - goBack(); - await delay(100); - } - navigate(Routes.WALLET_SCREEN); - return false; - } - }, - [ - accountAddress, - currentChainId, - debouncedIsHighPriceImpact, - flashbots, - gasFeeParamsBySpeed, - goBack, - inputAmount, - inputCurrency, - isBridgeSwap, - isCrosschainSwap, - isHardwareWallet, - navigate, - outputAmount, - outputCurrency, - priceImpact.percentDisplay, - selectedGasFee, - setParams, - tradeDetails, - type, - ] - ); - - const handleSubmit = useCallback(async () => { - const amountInUSD = '0'; - const NotificationManager = ios ? NativeModules.NotificationManager : null; - try { - // Tell iOS we're running a rap (for tracking purposes) - NotificationManager?.postNotification('rapInProgress'); - } catch (e) { - logger.error(new RainbowError(`[ExchangeModal]: error posting notification for rapInProgress: ${e}`)); - } finally { - const slippage = slippageInBips / 100; - analytics.track(`Submitted ${type}`, { - aggregator: tradeDetails?.source || '', - gasSetting: selectedGasFee?.option, - inputTokenAddress: inputCurrency?.address || '', - inputTokenName: inputCurrency?.name || '', - inputTokenSymbol: inputCurrency?.symbol || '', - isHardwareWallet, - isHighPriceImpact: debouncedIsHighPriceImpact, - legacyGasPrice: (selectedGasFee?.gasFeeParams as unknown as LegacyGasFeeParams)?.gasPrice?.amount || '', - liquiditySources: JSON.stringify(tradeDetails?.protocols || []), - maxNetworkFee: (selectedGasFee?.gasFee as GasFee)?.maxFee?.value?.amount || '', - network: chainsName[currentChainId], - networkFee: selectedGasFee?.gasFee?.estimatedFee?.value?.amount || '', - outputTokenAddress: outputCurrency?.address || '', - outputTokenName: outputCurrency?.name || '', - outputTokenSymbol: outputCurrency?.symbol || '', - priceImpact: priceImpact.percentDisplay, - slippage: isNaN(slippage) ? 'Error caclulating slippage.' : slippage, - type, - }); - } - - const outputInUSD = outputNativeAmount; - const gasPrice = - (selectedGasFee?.gasFee as GasFee)?.maxFee?.native?.value?.amount || - (selectedGasFee?.gasFee as LegacyGasFee)?.estimatedFee?.native?.value?.amount; - const cancelTransaction = await checkGasVsOutput(gasPrice, outputInUSD); - - if (cancelTransaction) { - return false; - } - try { - return await submit(amountInUSD); - } catch (e) { - return false; - } - }, [ - outputNativeAmount, - selectedGasFee?.gasFee, - selectedGasFee?.option, - selectedGasFee?.gasFeeParams, - slippageInBips, - type, - tradeDetails?.source, - tradeDetails?.protocols, - inputCurrency?.address, - inputCurrency?.name, - inputCurrency?.symbol, - isHardwareWallet, - debouncedIsHighPriceImpact, - currentChainId, - outputCurrency?.address, - outputCurrency?.name, - outputCurrency?.symbol, - priceImpact.percentDisplay, - submit, - ]); - - const confirmButtonProps = useMemoOne( - () => ({ - chainId: currentChainId, - disabled: !Number(inputAmount) || (!loading && !tradeDetails), - inputAmount, - isAuthorizing, - isHighPriceImpact: debouncedIsHighPriceImpact, - isSufficientBalance, - loading, - onSubmit: handleSubmit, - isHardwareWallet, - quoteError, - tradeDetails, - type, - isBridgeSwap, - }), - [ - loading, - handleSubmit, - inputAmount, - isAuthorizing, - debouncedIsHighPriceImpact, - testID, - tradeDetails, - type, - quoteError, - isSufficientBalance, - isBridgeSwap, - ] - ); - - const navigateToSwapSettingsSheet = useCallback(() => { - android && Keyboard.dismiss(); - const lastFocusedInputHandleTemporary = lastFocusedInputHandle.current; - android && (lastFocusedInputHandle.current = null); - inputFieldRef?.current?.blur(); - outputFieldRef?.current?.blur(); - nativeFieldRef?.current?.blur(); - const internalNavigate = () => { - delayNext(); - IS_ANDROID && keyboardListenerSubscription.current?.remove(); - setParams({ focused: false }); - navigate(Routes.SWAP_SETTINGS_SHEET, { - asset: outputCurrency, - chainId: currentChainId, - restoreFocusOnSwapModal: () => { - android && (lastFocusedInputHandle.current = lastFocusedInputHandleTemporary); - setParams({ focused: true }); - }, - swapSupportsFlashbots, - type: 'swap_settings', - }); - analytics.track('Opened Swap Settings'); - }; - if (IS_IOS || !isKeyboardOpen()) { - internalNavigate(); - } else { - keyboardListenerSubscription.current = Keyboard.addListener('keyboardDidHide', internalNavigate); - } - }, [ - lastFocusedInputHandle, - inputFieldRef, - outputFieldRef, - nativeFieldRef, - setParams, - navigate, - outputCurrency, - currentChainId, - swapSupportsFlashbots, - ]); - - const navigateToSwapDetailsModal = useCallback( - (isRefuelTx = false) => { - android && Keyboard.dismiss(); - const lastFocusedInputHandleTemporary = lastFocusedInputHandle.current; - android && (lastFocusedInputHandle.current = null); - inputFieldRef?.current?.blur?.(); - outputFieldRef?.current?.blur?.(); - nativeFieldRef?.current?.blur?.(); - const internalNavigate = () => { - IS_ANDROID && keyboardListenerSubscription.current?.remove(); - setParams({ focused: false }); - navigate(Routes.SWAP_DETAILS_SHEET, { - confirmButtonProps, - currentNetwork: chainsName[currentChainId], - flashbotTransaction: flashbots, - isRefuelTx, - restoreFocusOnSwapModal: () => { - android && (lastFocusedInputHandle.current = lastFocusedInputHandleTemporary); - setParams({ focused: true }); - }, - type: 'swap_details', - }); - analytics.track('Opened Swap Details modal', { - inputTokenAddress: inputCurrency?.address || '', - inputTokenName: inputCurrency?.name || '', - inputTokenSymbol: inputCurrency?.symbol || '', - outputTokenAddress: outputCurrency?.address || '', - outputTokenName: outputCurrency?.name || '', - outputTokenSymbol: outputCurrency?.symbol || '', - type, - }); - }; - if (IS_IOS || !isKeyboardOpen()) { - internalNavigate(); - } else { - keyboardListenerSubscription.current = Keyboard.addListener('keyboardDidHide', internalNavigate); - } - }, - [ - confirmButtonProps, - currentChainId, - flashbots, - inputCurrency?.address, - inputCurrency?.name, - inputCurrency?.symbol, - inputFieldRef, - lastFocusedInputHandle, - nativeFieldRef, - navigate, - outputCurrency?.address, - outputCurrency?.name, - outputCurrency?.symbol, - outputFieldRef, - setParams, - type, - ] - ); - - const handleTapWhileDisabled = useCallback(() => { - const lastFocusedInput = lastFocusedInputHandle?.current as unknown as TextInput; - lastFocusedInput?.blur(); - navigate(Routes.EXPLAIN_SHEET, { - inputToken: inputCurrency?.symbol, - fromChainId: inputChainId, - toChainId: outputChainId, - isCrosschainSwap, - isBridgeSwap, - onClose: () => { - InteractionManager.runAfterInteractions(() => { - setTimeout(() => { - lastFocusedInput?.focus(); - }, 250); - }); - }, - outputToken: outputCurrency?.symbol, - type: 'output_disabled', - }); - }, [ - inputCurrency?.symbol, - inputChainId, - isBridgeSwap, - isCrosschainSwap, - lastFocusedInputHandle, - navigate, - outputCurrency?.symbol, - outputChainId, - ]); - - const showConfirmButton = !!inputCurrency && !!outputCurrency; - - const handleConfirmExchangePress = useCallback(() => { - if (loading) return NOOP(); - - return navigateToSwapDetailsModal(); - }, [loading, navigateToSwapDetailsModal]); - - return ( - - - - - <> - - - - { - navigateToSelectInputCurrency(chainId); - }} - setInputAmount={updateInputAmount} - setNativeAmount={updateNativeAmount} - testID={`${testID}-input`} - updateAmountOnFocus={maxInputUpdate || flipCurrenciesUpdate || isFillingParams} - /> - { - navigateToSelectOutputCurrency(currentChainId); - }} - // eslint-disable-next-line react/jsx-props-no-spreading - {...(isCrosschainSwap && - !!outputCurrency && { - onTapWhileDisabled: handleTapWhileDisabled, - })} - loading={loading} - outputAmount={outputAmountDisplay} - outputCurrencyAddress={outputCurrency?.address} - outputCurrencyIcon={outputCurrency?.icon_url} - outputCurrencyColors={outputCurrency?.colors} - outputCurrencyMainnetAddress={outputCurrency?.mainnet_address} - outputCurrencySymbol={outputCurrency?.symbol} - outputFieldRef={outputFieldRef} - setOutputAmount={updateOutputAmount} - testID={`${testID}-output`} - updateAmountOnFocus={maxInputUpdate || flipCurrenciesUpdate || isFillingParams} - /> - - {showConfirmButton && ( - - )} - - - - - - - - {showConfirmButton && ( - - )} - - - - - - - - - ); -} diff --git a/src/screens/ExpandedAssetSheet.js b/src/screens/ExpandedAssetSheet.tsx similarity index 72% rename from src/screens/ExpandedAssetSheet.js rename to src/screens/ExpandedAssetSheet.tsx index fa1e90308ed..22063e5d462 100644 --- a/src/screens/ExpandedAssetSheet.js +++ b/src/screens/ExpandedAssetSheet.tsx @@ -1,27 +1,18 @@ -import { useRoute } from '@react-navigation/native'; +import { RouteProp, useRoute } from '@react-navigation/native'; import React, { createElement } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import TouchableBackdrop from '../components/TouchableBackdrop'; -import { - ChartExpandedState, - CustomGasState, - SwapDetailsState, - SwapSettingsState, - TokenIndexExpandedState, - UniqueTokenExpandedState, -} from '../components/expanded-state'; +import { CustomGasState, ChartExpandedState, UniqueTokenExpandedState } from '../components/expanded-state'; import { Centered } from '../components/layout'; import { useDimensions } from '@/hooks'; import { useNavigation } from '@/navigation'; import styled from '@/styled-thing'; import { position } from '@/styles'; +import { RootStackParamList } from '@/navigation/types'; const ScreenTypes = { custom_gas: CustomGasState, - swap_details: SwapDetailsState, - swap_settings: SwapSettingsState, token: ChartExpandedState, - token_index: TokenIndexExpandedState, unique_token: UniqueTokenExpandedState, }; @@ -31,18 +22,18 @@ const Container = styled(Centered).attrs({ direction: 'column', flex: 1, justifyContent: 'flex-end', -})(({ deviceHeight, height }) => ({ +})(({ deviceHeight, height }: { deviceHeight: number; height: number }) => ({ ...(height && { height: height + deviceHeight, }), ...position.coverAsObject, })); -export default function ExpandedAssetSheet(props) { +export default function ExpandedAssetSheet(props: any) { const { height: deviceHeight } = useDimensions(); const insets = useSafeAreaInsets(); const { goBack } = useNavigation(); - const { params } = useRoute(); + const { params } = useRoute>(); return ( diff --git a/src/screens/NFTSingleOfferSheet/index.tsx b/src/screens/NFTSingleOfferSheet/index.tsx index c5370caa023..fec9ccad360 100644 --- a/src/screens/NFTSingleOfferSheet/index.tsx +++ b/src/screens/NFTSingleOfferSheet/index.tsx @@ -48,11 +48,11 @@ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { chainsIdByName, chainsNativeAsset, defaultChains, getChainDefaultRpc } from '@/chains'; import { getNextNonce } from '@/state/nonces'; import { metadataPOSTClient } from '@/graphql'; import { ethUnits } from '@/references'; import { Transaction } from '@/graphql/__generated__/metadataPOST'; -import { chainsIdByName, chainsNativeAsset, defaultChains, getChainDefaultRpc } from '@/chains'; const NFT_IMAGE_HEIGHT = 160; const TWO_HOURS_MS = 2 * 60 * 60 * 1000; @@ -722,7 +722,6 @@ export function NFTSingleOfferSheet() { parentHorizontalPadding={28} showBiometryIcon={!insufficientEth} /> - {/* @ts-ignore */} { testID="send-confirmation-button" /> - {isENS && ( - - )} + {isENS && } diff --git a/src/screens/SendSheet.tsx b/src/screens/SendSheet.tsx index 27a0d6f7fa5..7b0e85a74d5 100644 --- a/src/screens/SendSheet.tsx +++ b/src/screens/SendSheet.tsx @@ -287,7 +287,7 @@ export default function SendSheet() { startPollingGasFees(currentChainId); }); } - }, [startPollingGasFees, selected?.chainId, prevChainId, currentChainId]); + }, [startPollingGasFees, prevChainId, currentChainId]); // Stop polling when the sheet is unmounted useEffect(() => { @@ -296,7 +296,7 @@ export default function SendSheet() { stopPollingGasFees(); }); }; - }, [stopPollingGasFees]); + }, [currentChainId, stopPollingGasFees]); useEffect(() => { const assetChainId = selected?.chainId; @@ -840,8 +840,7 @@ export default function SendSheet() { currentProviderChainId === currentChainId && toAddress && isValidAddress && - !isEmpty(selected) && - (isUniqueAsset || Number(amountDetails.assetAmount) >= 0) + !isEmpty(selected) ) { estimateGasLimit( { diff --git a/src/screens/SettingsSheet/components/MenuHeader.tsx b/src/screens/SettingsSheet/components/MenuHeader.tsx index 6c2cd8d1b65..fe3ee059881 100644 --- a/src/screens/SettingsSheet/components/MenuHeader.tsx +++ b/src/screens/SettingsSheet/components/MenuHeader.tsx @@ -104,7 +104,7 @@ const StatusIcon = ({ status, text }: StatusIconProps) => { marginTop={{ custom: 8 }} marginBottom={{ custom: 16 }} > - + {text}
diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index 24a48f39462..f4ef840c5fd 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -781,12 +781,7 @@ export const SignTransactionSheet = () => { chainId={chainId} theme={'dark'} marginBottom={0} - asset={undefined} fallbackColor={simulationResult?.simulationError ? colors.red : undefined} - testID={undefined} - showGasOptions={undefined} - validateGasParams={undefined} - crossChainServiceTime={undefined} />
)} diff --git a/src/screens/SwapModal.js b/src/screens/SwapModal.js deleted file mode 100644 index 5cf2e2dcc81..00000000000 --- a/src/screens/SwapModal.js +++ /dev/null @@ -1,28 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import React, { useEffect } from 'react'; -import ExchangeModal from './ExchangeModal'; -import { ExchangeModalTypes } from '@/helpers'; -import { useNavigation } from '@/navigation'; - -const SwapModal = (props, ref) => { - const { params = {} } = useRoute(); - const { ignoreInitialTypeCheck, fromDiscover } = params; - const { setParams } = useNavigation(); - useEffect(() => { - setParams({ focused: true }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - ); -}; - -export default React.forwardRef(SwapModal); diff --git a/src/screens/claimables/ClaimingClaimableSharedUI.tsx b/src/screens/claimables/ClaimingClaimableSharedUI.tsx index a788f349939..c4911e0e573 100644 --- a/src/screens/claimables/ClaimingClaimableSharedUI.tsx +++ b/src/screens/claimables/ClaimingClaimableSharedUI.tsx @@ -14,7 +14,7 @@ import { chainsLabel } from '@/chains'; import { useNavigation } from '@/navigation'; import { TextColor } from '@/design-system/color/palettes'; import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; -import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/helpers/utilities'; import { useAccountSettings, useWallets } from '@/hooks'; import { enableActionsOnReadOnlyWallet } from '@/config'; import { debounce } from 'lodash'; diff --git a/src/screens/claimables/ClaimingTransactionClaimable.tsx b/src/screens/claimables/ClaimingTransactionClaimable.tsx index 04106fbd7f4..cc3d5eaa85d 100644 --- a/src/screens/claimables/ClaimingTransactionClaimable.tsx +++ b/src/screens/claimables/ClaimingTransactionClaimable.tsx @@ -9,7 +9,7 @@ import { needsL1SecurityFeeChains } from '@/chains'; import { logger, RainbowError } from '@/logger'; import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI'; import { TransactionRequest } from '@ethersproject/providers'; -import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/helpers/utilities'; import { useMutation } from '@tanstack/react-query'; import { loadWallet } from '@/model/wallet'; import { walletExecuteRap } from '@/rapsV2/execute'; diff --git a/src/screens/discover/components/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 3f596cabf86..7934398f5af 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -7,7 +7,7 @@ import { useDebounce } from 'use-debounce'; import CurrencySelectionTypes from '@/helpers/currencySelectionTypes'; import deviceUtils from '@/utils/deviceUtils'; -import { CurrencySelectionList } from '@/components/exchange'; +import CurrencySelectionList from '@/components/CurrencySelectionList'; import { Row } from '@/components/layout'; import DiscoverSheetContext from '../DiscoverScreenContext'; import { analytics } from '@/analytics'; diff --git a/src/screens/mints/MintSheet.tsx b/src/screens/mints/MintSheet.tsx index ad974a04a93..953b0b1c539 100644 --- a/src/screens/mints/MintSheet.tsx +++ b/src/screens/mints/MintSheet.tsx @@ -52,11 +52,11 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { chainsName, defaultChains, getChainDefaultRpc } from '@/chains'; import { getNextNonce } from '@/state/nonces'; import { metadataPOSTClient } from '@/graphql'; import { Transaction } from '@/graphql/__generated__/metadataPOST'; import { ChainId } from '@/chains/types'; -import { chainsName, defaultChains, getChainDefaultRpc } from '@/chains'; const NFT_IMAGE_HEIGHT = 250; // inset * 2 -> 28 *2 @@ -626,7 +626,6 @@ const MintSheet = () => { /> - {/* @ts-ignore */} = ({ trans const { colors } = useTheme(); const hash = useMemo(() => ethereumUtils.getHash(transaction), [transaction]); const { network, status, chainId } = transaction; - const isReadOnly = useSelector((state: AppState) => state.wallets.selected?.type === WalletTypes.readOnly ?? true); + const isReadOnly = useSelector((state: AppState) => state.wallets.selected?.type === WalletTypes.readOnly); // Retry swap related data const retrySwapMetadata = useMemo(() => { const data = swapMetadataStorage.getString(hash ?? ''); @@ -44,13 +44,9 @@ export const TransactionDetailsHashAndActionsSection: React.FC = ({ trans const onRetrySwap = useCallback(() => { Navigation.handleAction(Routes.WALLET_SCREEN, {}); - Navigation.handleAction(Routes.EXCHANGE_MODAL, { - params: { - meta: retrySwapMetadata, - inputAsset: retrySwapMetadata?.inputAsset, - outputAsset: retrySwapMetadata?.outputAsset, - }, - }); + + // TODO: Add retry swap logic back for swaps + Navigation.handleAction(Routes.SWAP, {}); }, [retrySwapMetadata]); if (!hash || !network) { diff --git a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx index 15216791567..9b53b029a1a 100644 --- a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx @@ -14,7 +14,7 @@ import { CardSize } from '@/components/unique-token/CardSize'; import ImgixImage from '@/components/images/ImgixImage'; import { View } from 'react-native'; import ChainBadge from '@/components/coin-icon/ChainBadge'; -import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; +import { checkForPendingSwap } from '@/helpers/checkForPendingSwap'; import { ChainId } from '@/chains/types'; type Props = { diff --git a/src/screens/transaction-details/components/TransactionMasthead.tsx b/src/screens/transaction-details/components/TransactionMasthead.tsx index 6516a3179d0..0c8a450564c 100644 --- a/src/screens/transaction-details/components/TransactionMasthead.tsx +++ b/src/screens/transaction-details/components/TransactionMasthead.tsx @@ -33,7 +33,7 @@ import { addressHashedColorIndex, addressHashedEmoji } from '@/utils/profileUtil import ImageAvatar from '@/components/contacts/ImageAvatar'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import * as lang from '@/languages'; -import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; +import { checkForPendingSwap } from '@/helpers/checkForPendingSwap'; import { ChainId } from '@/chains/types'; const TransactionMastheadHeight = android ? 153 : 135; diff --git a/src/state/browser/browserStore.ts b/src/state/browser/browserStore.ts index 041913d506e..ee3bf8f7be3 100644 --- a/src/state/browser/browserStore.ts +++ b/src/state/browser/browserStore.ts @@ -2,7 +2,7 @@ import { debounce } from 'lodash'; import { MMKV } from 'react-native-mmkv'; import { create } from 'zustand'; import { PersistStorage, StorageValue, persist, subscribeWithSelector } from 'zustand/middleware'; -import { DEFAULT_TAB_URL } from '@/components/DappBrowser/constants'; +import { RAINBOW_HOME } from '@/components/DappBrowser/constants'; import { TabData, TabId } from '@/components/DappBrowser/types'; import { generateUniqueId, normalizeUrl } from '@/components/DappBrowser/utils'; import { RainbowError, logger } from '@/logger'; @@ -105,8 +105,8 @@ const persistedBrowserStorage: PersistStorage = { const INITIAL_ACTIVE_TAB_INDEX = 0; const INITIAL_TAB_IDS = [generateUniqueId()]; -const INITIAL_TABS_DATA = new Map([[INITIAL_TAB_IDS[0], { url: DEFAULT_TAB_URL }]]); -const INITIAL_PERSISTED_TAB_URLS: Record = { [INITIAL_TAB_IDS[0]]: DEFAULT_TAB_URL }; +const INITIAL_TABS_DATA = new Map([[INITIAL_TAB_IDS[0], { url: RAINBOW_HOME }]]); +const INITIAL_PERSISTED_TAB_URLS: Record = { [INITIAL_TAB_IDS[0]]: RAINBOW_HOME }; interface BrowserStore { activeTabIndex: number; @@ -146,7 +146,7 @@ export const useBrowserStore = create()( getActiveTabUrl: () => get().persistedTabUrls[get().getActiveTabId()], - getTabData: (tabId: string) => get().tabsData.get(tabId) || { url: DEFAULT_TAB_URL }, + getTabData: (tabId: string) => get().tabsData.get(tabId) || { url: RAINBOW_HOME }, goToPage: (url: string, tabId?: string) => set(state => { @@ -187,7 +187,7 @@ export const useBrowserStore = create()( const existingTabIds = state.tabIds.filter(id => newTabIds.includes(id)); const addedTabIds = newTabIds.filter(id => !state.tabIds.includes(id)); const newTabsData = new Map(state.tabsData); - addedTabIds.forEach(id => newTabsData.set(id, { url: DEFAULT_TAB_URL })); + addedTabIds.forEach(id => newTabsData.set(id, { url: RAINBOW_HOME })); return { tabIds: [...existingTabIds, ...addedTabIds], tabsData: newTabsData }; }), diff --git a/src/state/gas/gasStore.ts b/src/state/gas/gasStore.ts deleted file mode 100644 index b8357f1dcd4..00000000000 --- a/src/state/gas/gasStore.ts +++ /dev/null @@ -1,79 +0,0 @@ -import create from 'zustand'; - -// todo - absolute imports -import { GasFeeLegacyParams, GasFeeLegacyParamsBySpeed, GasFeeParams, GasFeeParamsBySpeed, GasSpeed } from '../../__swaps__/types/gas'; - -import { createStore } from '../internal/createStore'; -import { buildLocalizedTimeUnitString } from '@/__swaps__/utils/time'; -import { gasUtils } from '@/utils'; - -export interface GasStore { - selectedGas: GasFeeParams | GasFeeLegacyParams; - gasFeeParamsBySpeed: GasFeeParamsBySpeed | GasFeeLegacyParamsBySpeed; - customGasModified: boolean; - setCustomSpeed: (speed: GasFeeParams) => void; - setSelectedGas: ({ selectedGas }: { selectedGas: GasFeeParams | GasFeeLegacyParams }) => void; - setGasFeeParamsBySpeed: ({ gasFeeParamsBySpeed }: { gasFeeParamsBySpeed: GasFeeParamsBySpeed | GasFeeLegacyParamsBySpeed }) => void; - clearCustomGasModified: () => void; -} - -export const gasStore = createStore( - (set, get) => ({ - selectedGas: { - maxBaseFee: { - amount: '0', - display: '0.01', - gwei: '0', - }, - maxPriorityFeePerGas: { - amount: '0', - display: '0', - gwei: '0', - }, - option: GasSpeed.FAST, - estimatedTime: { - amount: 12, - display: buildLocalizedTimeUnitString({ plural: true, short: true, unit: '12' }), - }, - display: gasUtils.getGasLabel(gasUtils.FAST), - transactionGasParams: { - maxPriorityFeePerGas: '0', - maxFeePerGas: '0', - }, - gasFee: {}, - } as GasFeeParams | GasFeeLegacyParams, - gasFeeParamsBySpeed: {} as GasFeeParamsBySpeed | GasFeeLegacyParamsBySpeed, - customGasModified: false, - setSelectedGas: ({ selectedGas }) => { - set({ - selectedGas, - }); - }, - setGasFeeParamsBySpeed: ({ gasFeeParamsBySpeed }) => { - set({ - gasFeeParamsBySpeed, - }); - }, - setCustomSpeed: (speed: GasFeeParams) => { - const { gasFeeParamsBySpeed } = get(); - set({ - gasFeeParamsBySpeed: { - ...gasFeeParamsBySpeed, - [GasSpeed.CUSTOM]: speed, - } as GasFeeParamsBySpeed, - customGasModified: true, - }); - }, - clearCustomGasModified: () => { - set({ customGasModified: false }); - }, - }), - { - persist: { - name: 'gas', - version: 0, - }, - } -); - -export const useGasStore = create(gasStore); diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index 1ff21fe247a..a16be52941b 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -6,7 +6,7 @@ import { ChainId } from '@/chains/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { RecentSwap } from '@/__swaps__/types/swap'; import { getCachedGasSuggestions } from '@/__swaps__/utils/meteorology'; -import { lessThan } from '@/__swaps__/utils/numbers'; +import { lessThan } from '@/helpers/utilities'; import { getDefaultSlippage } from '@/__swaps__/utils/swaps'; import { RainbowError, logger } from '@/logger'; import { getRemoteConfig } from '@/model/remoteConfig'; diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index 978e41ace2b..daf8cbb0d66 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -44,6 +44,44 @@ import { AddressOrEth } from '@/__swaps__/types/assets'; import { chainsIdByName, chainsName, chainsNativeAsset, defaultChains, getChainGasUnits } from '@/chains'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; +/** + * @deprecated - use `getUniqueId` instead for chainIds + * @desc Get the unique ID for an address and network + * @param address - The address to get the unique ID for + * @param network - The network to get the unique ID for + * @returns `${address}_${network}` + */ +export const getUniqueIdNetwork = (address: EthereumAddress, network: Network) => `${address}_${network}`; + +export const getUniqueId = (address: EthereumAddress, chainId: ChainId) => { + 'worklet'; + return `${address}_${chainId}`; +}; + +/** + * @desc Get the address and chainId from a unique ID + * @param uniqueId - The unique ID to get the address & (chainId || network) from + * @returns { address: AddressOrEth; chainId: ChainId } + */ +export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: AddressOrEth; chainId: ChainId } => { + const parts = uniqueId.split('_'); + + // If the unique ID does not contain '_', it's a mainnet address + if (parts.length === 1) { + return { address: parts[0] as AddressOrEth, chainId: ChainId.mainnet }; + } + + const address = parts[0] as AddressOrEth; + const networkOrChainId = parts[1]; + // if the second part is a string, it's probably a network + if (isNaN(Number(networkOrChainId))) { + const chainId = chainsIdByName[networkOrChainId] || ChainId.mainnet; // Default to mainnet if unknown + return { address, chainId }; + } + + return { address, chainId: +networkOrChainId }; +}; + const getNetworkNativeAsset = ({ chainId }: { chainId: ChainId }) => { const nativeAssetAddress = chainsNativeAsset[chainId].address; const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, chainId); @@ -421,26 +459,6 @@ async function parseEthereumUrl(data: string) { }); } -export const getUniqueIdNetwork = (address: EthereumAddress, network: Network) => `${address}_${network}`; - -export const getUniqueId = (address: EthereumAddress, chainId: ChainId) => `${address}_${chainId}`; - -export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: AddressOrEth; chainId: ChainId } => { - const parts = uniqueId.split('_'); - - // If the unique ID does not contain '_', it's a mainnet address - if (parts.length === 1) { - return { address: parts[0] as AddressOrEth, chainId: ChainId.mainnet }; - } - - // If the unique ID contains '_', the last part is the network and the rest is the address - const network = parts[1] as Network; // Assuming the last part is a valid Network enum value - const address = parts[0] as AddressOrEth; - const chainId = chainsIdByName[network]; - - return { address, chainId }; -}; - const calculateL1FeeOptimism = async ( tx: RainbowTransaction | TransactionRequest, provider: StaticJsonRpcProvider diff --git a/src/utils/isLowerCaseMatch.ts b/src/utils/isLowerCaseMatch.ts index 24cd7c45ed4..3404eaaa706 100644 --- a/src/utils/isLowerCaseMatch.ts +++ b/src/utils/isLowerCaseMatch.ts @@ -1,3 +1,3 @@ -export default function isLowerCaseMatch(a: string, b: string) { +export default function isLowerCaseMatch(a: string, b?: string) { return a?.toLowerCase() === b?.toLowerCase(); }