From d408dfede8913baaf7608276b0a16244ec89d751 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 28 May 2024 14:18:35 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screens/Swap/components/GasButton.tsx | 32 +++++++------- .../screens/Swap/components/GasPanel.tsx | 15 ++++++- .../screens/Swap/components/ReviewPanel.tsx | 2 +- .../screens/Swap/hooks/useEstimatedGasFee.ts | 44 +++++++++++++------ .../Swap/hooks/useSwapEstimatedGasLimit.ts | 9 ++-- src/__swaps__/utils/meteorology.ts | 4 +- 6 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index ae9fa1620ae..3966c08f464 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -1,18 +1,19 @@ import { ChainId } from '@/__swaps__/types/chains'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; -import { useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; +import { getCachedCurrentBaseFee, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; import { add } from '@/__swaps__/utils/numbers'; import { ButtonPressAnimation } from '@/components/animations'; import { ContextMenu } from '@/components/context-menu'; import { Centered } from '@/components/layout'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import { Box, Inline, Stack, Text, TextIcon, useColorMode, useForegroundColor } from '@/design-system'; +import { Box, Inline, Text, TextIcon, useColorMode, useForegroundColor } from '@/design-system'; import { IS_ANDROID } from '@/env'; import * as i18n from '@/languages'; import { useSwapsStore } from '@/state/swaps/swapsStore'; import styled from '@/styled-thing'; import { gasUtils } from '@/utils'; import React, { ReactNode, useCallback, useMemo } from 'react'; +import { StyleSheet } from 'react-native'; import { runOnUI } from 'react-native-reanimated'; import { ETH_COLOR, ETH_COLOR_DARK, THICK_BORDER_WIDTH } from '../constants'; import { formatNumber } from '../hooks/formatNumber'; @@ -20,21 +21,20 @@ import { GasSettings, useCustomGasSettings } from '../hooks/useCustomGas'; import { useSwapEstimatedGasFee } from '../hooks/useEstimatedGasFee'; import { GasSpeed, setSelectedGasSpeed, useSelectedGas, useSelectedGasSpeed } from '../hooks/useSelectedGas'; import { useSwapContext } from '../providers/swap-provider'; -import { StyleSheet } from 'react-native'; const { GAS_ICONS } = gasUtils; function EstimatedGasFee() { const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const gasSettings = useSelectedGas(chainId); - const estimatedGasFee = useSwapEstimatedGasFee(gasSettings); + const { data: estimatedGasFee, isLoading } = useSwapEstimatedGasFee(gasSettings); return ( 􀵟 - + {estimatedGasFee} @@ -66,17 +66,19 @@ const GasSpeedPagerCentered = styled(Centered).attrs(() => ({ marginHorizontal: 8, }))({}); -function getEstimatedFeeRangeInGwei(gasSettings: GasSettings | undefined, currentBaseFee?: string | undefined) { +function getEstimatedFeeRangeInGwei(gasSettings: GasSettings | undefined, currentBaseFee: string | undefined) { if (!gasSettings) return undefined; if (!gasSettings.isEIP1559) return `${formatNumber(weiToGwei(gasSettings.gasPrice))} Gwei`; const { maxBaseFee, maxPriorityFee } = gasSettings; - return `${formatNumber(weiToGwei(add(maxBaseFee, maxPriorityFee)))} Gwei`; + const maxFee = formatNumber(weiToGwei(add(maxBaseFee, maxPriorityFee))); + + if (!currentBaseFee) return `${maxFee} Gwei`; + + const minFee = formatNumber(weiToGwei(add(currentBaseFee, maxPriorityFee))); - // return `${formatNumber(weiToGwei(add(baseFee, maxPriorityFee)))} - ${formatNumber( - // weiToGwei(add(maxBaseFee, maxPriorityFee)) - // )} Gwei`; + return `${minFee} - ${maxFee} Gwei`; } function keys(obj: Record | undefined) { @@ -118,9 +120,9 @@ const GasMenu = ({ children }: { children: ReactNode }) => { const menuItems = menuOptions.map(gasOption => { if (IS_ANDROID) return gasOption; - // const currentBaseFee = getCachedCurrentBaseFee(chainId); + const currentBaseFee = getCachedCurrentBaseFee(chainId); const gasSettings = gasOption === 'custom' ? customGasSettings : metereologySuggestions.data?.[gasOption]; - const subtitle = getEstimatedFeeRangeInGwei(gasSettings); + const subtitle = getEstimatedFeeRangeInGwei(gasSettings, currentBaseFee); return { actionKey: gasOption, @@ -130,7 +132,7 @@ const GasMenu = ({ children }: { children: ReactNode }) => { }; }); return { menuItems, menuTitle: '' }; - }, [customGasSettings, menuOptions, metereologySuggestions.data]); + }, [customGasSettings, menuOptions, metereologySuggestions.data, chainId]); if (metereologySuggestions.isLoading) return children; @@ -211,10 +213,10 @@ export function ReviewGasButton() { export const GasButton = () => { return ( - + - + ); }; diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index b4998da78bb..a4284e6a4b3 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -144,6 +144,9 @@ function CurrentBaseFee() { // loading state? + const isEIP1559 = useIsChainEIP1559(chainId); + if (!isEIP1559) return null; + return ( select(s?.[chainId])); const speed = useSelectedGasSpeed(chainId); - const { data: suggestion } = useMeteorologySuggestion({ chainId, speed, select, enabled: !!state }); + const { data: suggestion } = useMeteorologySuggestion({ + chainId, + speed, + select, + enabled: !!state, + notifyOnChangeProps: !!state && speed !== 'custom' ? ['data'] : [], + }); return useMemo(() => state ?? currentGasSettings ?? suggestion, [currentGasSettings, state, suggestion]); } @@ -259,7 +268,7 @@ function MaxTransactionFee() { const gasPanelState = useGasPanelState(); const gasSettings = useMemo(() => stateToGasSettings(gasPanelState), [gasPanelState]); - const maxTransactionFee = useSwapEstimatedGasFee(gasSettings); + const { data: maxTransactionFee } = useSwapEstimatedGasFee(gasSettings); return ( @@ -286,7 +295,9 @@ function MaxTransactionFee() { function EditableGasSettings() { const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const isEIP1559 = useIsChainEIP1559(chainId); + if (!isEIP1559) return ; + return ( <> diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 392c632d1f0..14cf3f97c1b 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -89,7 +89,7 @@ const RainbowFee = () => { function EstimatedGasFee() { const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const gasSettings = useSelectedGas(chainId); - const estimatedGasFee = useSwapEstimatedGasFee(gasSettings); + const { data: estimatedGasFee } = useSwapEstimatedGasFee(gasSettings); return ( diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index 166c6e382ec..4e92d565f71 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -3,10 +3,10 @@ import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { add, multiply } from '@/__swaps__/utils/numbers'; import { useSwapsStore } from '@/state/swaps/swapsStore'; import ethereumUtils, { useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import { useMemo } from 'react'; import { formatUnits } from 'viem'; import { formatCurrency, formatNumber } from './formatNumber'; import { GasSettings } from './useCustomGas'; -import { useDebounce } from './useDebounce'; import { useSwapEstimatedGasLimit } from './useSwapEstimatedGasLimit'; function safeBigInt(value: string) { @@ -29,31 +29,47 @@ export function useEstimatedGasFee({ const network = ethereumUtils.getNetworkFromChainId(chainId); const nativeNetworkAsset = useNativeAssetForNetwork(network); - if (!gasLimit || !gasSettings || !nativeNetworkAsset) return 'Loading...'; // TODO: loading state - const amount = gasSettings.isEIP1559 ? add(gasSettings.maxBaseFee, gasSettings.maxPriorityFee) : gasSettings.gasPrice; + return useMemo(() => { + if (!gasLimit || !gasSettings || !nativeNetworkAsset?.price) return; - const totalWei = multiply(gasLimit, amount); - const networkAssetPrice = nativeNetworkAsset.price.value?.toString(); + const amount = gasSettings.isEIP1559 ? add(gasSettings.maxBaseFee, gasSettings.maxPriorityFee) : gasSettings.gasPrice; - if (!networkAssetPrice) return `${formatNumber(weiToGwei(totalWei))} Gwei`; + const totalWei = multiply(gasLimit, amount); + const networkAssetPrice = nativeNetworkAsset.price.value?.toString(); - const gasAmount = formatUnits(safeBigInt(totalWei), nativeNetworkAsset.decimals).toString(); - const feeInUserCurrency = multiply(networkAssetPrice, gasAmount); + if (!networkAssetPrice) return `${formatNumber(weiToGwei(totalWei))} Gwei`; - return formatCurrency(feeInUserCurrency); + const gasAmount = formatUnits(safeBigInt(totalWei), nativeNetworkAsset.decimals).toString(); + const feeInUserCurrency = multiply(networkAssetPrice, gasAmount); + + return formatCurrency(feeInUserCurrency); + }, [gasLimit, gasSettings, nativeNetworkAsset]); } +const isSameAddress = (a: string, b: string) => a.toLowerCase() === b.toLowerCase(); export function useSwapEstimatedGasFee(gasSettings: GasSettings | undefined) { const chainId = useSwapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); const assetToSell = useSwapsStore(s => s.inputAsset); + const assetToBuy = useSwapsStore(s => s.outputAsset); const quote = useSwapsStore(s => s.quote); - const debouncedQuote = useDebounce(quote, 200); - - const { data: gasLimit } = useSwapEstimatedGasLimit({ chainId, quote: debouncedQuote, assetToSell }, { enabled: !!debouncedQuote }); + const { data: gasLimit, isFetching } = useSwapEstimatedGasLimit( + { chainId, quote, assetToSell }, + { + enabled: + !!quote && + !!assetToSell && + !!assetToBuy && + !('error' in quote) && + // the quote and the input/output assets are not updated together, + // we shouldn't try to estimate if the assets are not the same as the quote (probably still fetching a quote) + isSameAddress(quote.sellTokenAddress, assetToSell.address) && + isSameAddress(quote.buyTokenAddress, assetToBuy.address), + } + ); - // useWhyDidYouUpdate('useSwapEstimatedGasFee', { chainId, gasLimit, gasSettings, assetToSell, quote }); + const estimatedFee = useEstimatedGasFee({ chainId, gasLimit, gasSettings }); - return useEstimatedGasFee({ chainId, gasLimit, gasSettings }); + return useMemo(() => ({ isLoading: isFetching, data: estimatedFee }), [estimatedFee, isFetching]); } diff --git a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts index 0382875810f..c30913f3da9 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts @@ -25,7 +25,7 @@ export type EstimateSwapGasLimitArgs = { // Query Key const estimateSwapGasLimitQueryKey = ({ chainId, quote, assetToSell }: EstimateSwapGasLimitArgs) => - createQueryKey('estimateSwapGasLimit', { chainId, quote, assetToSell }, { persisterVersion: 1 }); + createQueryKey('estimateSwapGasLimit', { chainId, quote, assetToSell }); type EstimateSwapGasLimitQueryKey = ReturnType; @@ -94,10 +94,11 @@ export function useSwapEstimatedGasLimit( }), estimateSwapGasLimitQueryFunction, { + staleTime: 30 * 1000, // 30s + cacheTime: 60 * 1000, // 1min + notifyOnChangeProps: ['data', 'isFetching'], keepPreviousData: true, - staleTime: 12000, - cacheTime: Infinity, - notifyOnChangeProps: ['data'], + placeholderData: gasUnits.basic_swap[chainId], ...config, } ); diff --git a/src/__swaps__/utils/meteorology.ts b/src/__swaps__/utils/meteorology.ts index 30d47c16490..cff7bc1b5f3 100644 --- a/src/__swaps__/utils/meteorology.ts +++ b/src/__swaps__/utils/meteorology.ts @@ -227,11 +227,13 @@ export function useMeteorologySuggestion s as Selected, + notifyOnChangeProps = ['data'], }: { chainId: ChainId; speed: GasSpeed; enabled?: boolean; select?: (d: GasSuggestions[keyof GasSuggestions] | undefined) => Selected; + notifyOnChangeProps?: ['data'] | []; }) { return useMeteorology( { chainId }, @@ -241,7 +243,7 @@ export function useMeteorologySuggestion