From 61306eb866bb412bbac303c9aa1c50ca371c09ae Mon Sep 17 00:00:00 2001 From: Aren Date: Tue, 1 Oct 2024 21:05:02 +0400 Subject: [PATCH 1/2] Some fixes in fetching balances and gases --- components/Input/CurrencyFormField.tsx | 19 +++++ components/Input/dynamic/MinMax.tsx | 18 ++--- hooks/useBalance.ts | 106 +++++++++++++++---------- hooks/useIsWindowVisible.ts | 33 ++++++++ lib/balances/evm/useEVMBalance.ts | 4 +- 5 files changed, 127 insertions(+), 53 deletions(-) create mode 100644 hooks/useIsWindowVisible.ts diff --git a/components/Input/CurrencyFormField.tsx b/components/Input/CurrencyFormField.tsx index 08d374ec4..5e60f057e 100644 --- a/components/Input/CurrencyFormField.tsx +++ b/components/Input/CurrencyFormField.tsx @@ -19,6 +19,7 @@ import { resolveNetworkRoutesURL } from "../../helpers/routes"; import useWallet from "../../hooks/useWallet"; import { ONE_WEEK } from "./NetworkFormField"; import RouteIcon from "./RouteIcon"; +import useBalance from "../../hooks/useBalance"; const BalanceComponent = dynamic(() => import("./dynamic/Balance"), { loading: () => <>, @@ -34,6 +35,7 @@ const CurrencyFormField: FC<{ direction: SwapDirection }> = ({ direction }) => { const name = direction === 'from' ? 'fromCurrency' : 'toCurrency'; const query = useQueryState() const { balances } = useBalancesState() + const { fetchBalance } = useBalance() const { getAutofillProvider: getProvider } = useWallet() @@ -140,6 +142,23 @@ const CurrencyFormField: FC<{ direction: SwapDirection }> = ({ direction }) => { }, [toCurrency, currencyGroup, name, from, routes, error, isLoading]) + const network = direction === 'from' ? from : to + const token = direction === 'from' ? fromCurrency : toCurrency + useEffect(() => { + let balanceGetHandler: any = undefined + if (network && token) { + (async () => { + balanceGetHandler = setInterval(async () => { + await fetchBalance(network, token); + }, 60000) + })() + } + return () => { + clearInterval(balanceGetHandler) + } + }, [network, token]) + + const handleSelect = useCallback((item: SelectMenuItem) => { setFieldValue(name, item.baseObject, true) }, [name, direction, toCurrency, fromCurrency, from, to]) diff --git a/components/Input/dynamic/MinMax.tsx b/components/Input/dynamic/MinMax.tsx index 79981963a..2a941fadb 100644 --- a/components/Input/dynamic/MinMax.tsx +++ b/components/Input/dynamic/MinMax.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo } from "react" +import { useCallback, useEffect, useMemo, useState } from "react" import useWallet from "../../../hooks/useWallet" import SecondaryButton from "../../buttons/secondaryButton" import { useFormikContext } from "formik"; @@ -13,7 +13,7 @@ const MinMax = ({ onAddressGet }: { onAddressGet: (address: string) => void }) = const { values, setFieldValue } = useFormikContext(); const { fromCurrency, from, destination_address } = values || {}; const { minAllowedAmount, maxAllowedAmount: maxAmountFromApi } = useFee() - const { balances, gases } = useBalancesState() + const { balances, gases, isBalanceLoading, isGasLoading } = useBalancesState() const query = useQueryState() const { getAutofillProvider: getProvider } = useWallet() @@ -21,7 +21,7 @@ const MinMax = ({ onAddressGet }: { onAddressGet: (address: string) => void }) = return from && getProvider(from) }, [from, getProvider]) - const { fetchNetworkBalances, fetchGas } = useBalance() + const { fetchBalance, fetchGas } = useBalance() const wallet = provider?.getConnectedWallet(values.from) @@ -55,13 +55,13 @@ const MinMax = ({ onAddressGet }: { onAddressGet: (address: string) => void }) = maxAllowedAmount = Number(maxAmountFromApi) || 0 } - const handleSetMaxAmount = useCallback(async () => { + const handleSetMaxAmount = async () => { setFieldValue('amount', maxAllowedAmount); - from && fetchNetworkBalances(from); - - from && fromCurrency && fetchGas(from, fromCurrency, wallet?.address || destination_address || ""); - - }, [from, fromCurrency, destination_address, maxAllowedAmount]) + if (from && fromCurrency) { + if (!isBalanceLoading) await fetchBalance(from, fromCurrency); + if (!isGasLoading) await fetchGas(from, fromCurrency, destination_address || wallet?.address || ""); + } + } useEffect(() => { wallet?.address && onAddressGet(wallet.address) diff --git a/hooks/useBalance.ts b/hooks/useBalance.ts index 00f0035fb..ff8ff6978 100644 --- a/hooks/useBalance.ts +++ b/hooks/useBalance.ts @@ -10,6 +10,7 @@ import { useBalancesState, useBalancesUpdate } from "../context/balances" import { Network, NetworkWithTokens, Token } from "../Models/Network" import { useQueryState } from "../context/query" import useTonBalance from "../lib/balances/ton/useTonBalance" +import useIsWindowVisible from "./useIsWindowVisible" export default function useBalanceProvider() { @@ -23,7 +24,7 @@ export default function useBalanceProvider() { useTonBalance() ] - const { balances, gases } = useBalancesState() + const { balances, gases, isBalanceLoading } = useBalancesState() const query = useQueryState() const { @@ -34,87 +35,106 @@ export default function useBalanceProvider() { } = useBalancesUpdate() const { getAutofillProvider } = useWallet() + const isWindowVisible = useIsWindowVisible() const fetchNetworkBalances = async (network: NetworkWithTokens, address?: string) => { + if (!isWindowVisible) return + const provider = getAutofillProvider(network) const wallet = provider?.getConnectedWallet(network) address = address || query.account || wallet?.address - const balance = balances[address || '']?.find(b => b?.network === network?.name) - const isBalanceOutDated = !balance || new Date().getTime() - (new Date(balance.request_time).getTime() || 0) > 10000 + const balance = address ? balances[address]?.find(b => b?.network === network?.name) : undefined + const isBalanceOutDated = !balance || (balance.request_time ? (new Date().getTime() - new Date(balance.request_time).getTime() > 15000) : true) if (network && isBalanceOutDated && address) { - setIsBalanceLoading(true) - - const provider = getBalanceProvider(network) - const networkBalances = await provider?.getNetworkBalances({ - networkName: network.name, - address: address, - }) || [] - - setAllBalances((data) => { - const walletBalances = data[address] - const otherNetworkBalances = walletBalances?.filter(b => b?.network !== network.name) || [] - return { ...data, [address]: [...otherNetworkBalances, ...networkBalances] } - }) - setIsBalanceLoading(false) + try { + setIsBalanceLoading(true) + + const provider = getBalanceProvider(network) + const networkBalances = await provider?.getNetworkBalances({ + networkName: network.name, + address: address, + }) + + if (networkBalances) { + setAllBalances((data) => { + const walletBalances = data[address] + const otherNetworkBalances = walletBalances?.filter(b => b?.network !== network.name) || [] + return { ...data, [address]: [...otherNetworkBalances, ...networkBalances] } + }) + } + + } + catch (e) { console.log(e) } + finally { setIsBalanceLoading(false) } } } const fetchBalance = async (network: Network, token: Token, address?: string) => { + if (!isWindowVisible) return + const provider = getAutofillProvider(network) const wallet = provider?.getConnectedWallet(network) address = address || query.account || wallet?.address - const balance = balances[address || '']?.find(b => b?.network === network?.name) - const isBalanceOutDated = !balance || new Date().getTime() - (new Date(balance.request_time).getTime() || 0) > 10000 + const balance = balances[address || '']?.find(b => b?.network === network?.name && b?.token === token?.symbol) + const isBalanceOutDated = !balance || (balance.request_time ? (new Date().getTime() - new Date(balance.request_time).getTime() > 15000) : true) if (network && isBalanceOutDated && address) { - setIsBalanceLoading(true) - - const provider = getBalanceProvider(network) - const balance = await provider?.getBalance({ - networkName: network.name, - address: address, - token - }) - - setAllBalances((data) => { - const walletBalances = data[address] - const filteredBalances = walletBalances?.filter(b => !(b?.network === network?.name && b?.token === token?.symbol)) || [] - return { ...data, [address]: filteredBalances?.concat(balance || []) } - }) - setIsBalanceLoading(false) - - return balance + try { + setIsBalanceLoading(true) + debugger + + const provider = getBalanceProvider(network) + const newBalance = await provider?.getBalance({ + networkName: network.name, + address: address, + token + }) + + setAllBalances((data) => { + const walletBalances = data[address] + const filteredBalances = walletBalances?.filter(b => !(b?.network === network?.name && b?.token === token?.symbol)) || [] + return { ...data, [address]: filteredBalances?.concat(newBalance || []) } + }) + + return newBalance + + + } + catch (e) { console.log(e) } + finally { setIsBalanceLoading(false) } } return balance } const fetchGas = async (network: Network, token: Token, userDestinationAddress: string, recipientAddress?: string) => { - - if (!network) { + if (!network || !isWindowVisible) { return } const destination_address = userDestinationAddress as `0x${string}` const gas = gases[network.name]?.find(g => g?.token === token?.symbol) - const isGasOutDated = !gas || new Date().getTime() - (new Date(gas.request_time).getTime() || 0) > 10000 + const isGasOutDated = !gas || (gas.request_time ? (new Date().getTime() - new Date(gas.request_time).getTime() > 15000) : true) const provider = getAutofillProvider(network) const wallet = provider?.getConnectedWallet(network) - if (isGasOutDated + if ( + isGasOutDated && token && wallet?.address - && destination_address) { - setIsGasLoading(true) + && destination_address + ) { try { + setIsGasLoading(true) + const gasProvider = getBalanceProvider(network) const gas = gasProvider?.getGas && await gasProvider?.getGas({ address: wallet?.address as `0x${string}`, @@ -123,7 +143,7 @@ export default function useBalanceProvider() { recipientAddress, isSweeplessTx: wallet.address !== userDestinationAddress, wallet - }) || [] + }) if (gas) { setAllGases((data) => { diff --git a/hooks/useIsWindowVisible.ts b/hooks/useIsWindowVisible.ts new file mode 100644 index 000000000..53ce8ee71 --- /dev/null +++ b/hooks/useIsWindowVisible.ts @@ -0,0 +1,33 @@ +import { useCallback, useEffect, useState } from 'react' + +function isVisibilityStateSupported() { + return 'visibilityState' in document +} + +function isWindowVisible() { + return !isVisibilityStateSupported() || document.visibilityState !== 'hidden' +} + +/** + * Returns whether the window is currently visible to the user. + */ +export default function useIsWindowVisible(): boolean { + const [focused, setFocused] = useState(false) + const listener = useCallback(() => { + setFocused(isWindowVisible()) + }, [setFocused]) + + useEffect(() => { + if (!isVisibilityStateSupported()) { + return undefined + } + setFocused(() => isWindowVisible()) + + document.addEventListener('visibilitychange', listener) + return () => { + document.removeEventListener('visibilitychange', listener) + } + }, [listener]) + + return focused +} \ No newline at end of file diff --git a/lib/balances/evm/useEVMBalance.ts b/lib/balances/evm/useEVMBalance.ts index 4b38a0884..47c09101b 100644 --- a/lib/balances/evm/useEVMBalance.ts +++ b/lib/balances/evm/useEVMBalance.ts @@ -137,7 +137,9 @@ export default function useEVMBalance(): BalanceProvider { const gas = await gasProvider.resolveGas() - return [gas!] + if (!gas) return + + return [gas] } catch (e) { From f4454d2dfd8e68aac7660b4d82b7440e3442b578 Mon Sep 17 00:00:00 2001 From: Aren Date: Wed, 2 Oct 2024 16:08:36 +0400 Subject: [PATCH 2/2] fetch token balance on swap modal close --- components/Swap/Form/index.tsx | 5 +++++ hooks/useBalance.ts | 1 - next-env.d.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/Swap/Form/index.tsx b/components/Swap/Form/index.tsx index 064cc4ef2..1f1ae4192 100644 --- a/components/Swap/Form/index.tsx +++ b/components/Swap/Form/index.tsx @@ -37,6 +37,7 @@ import { AddressGroup } from "../../Input/Address/AddressPicker"; import { useAsyncModal } from "../../../context/asyncModal"; import { ValidationProvider } from "../../../context/validationErrorContext"; import { TrackEvent } from "../../../pages/_document"; +import useBalance from "../../../hooks/useBalance"; type NetworkToConnect = { DisplayName: string; @@ -65,6 +66,7 @@ export default function Form() { const { getSourceProvider } = useWallet() const addresses = useAddressesStore(state => state.addresses) const { getConfirmation } = useAsyncModal(); + const { fetchBalance } = useBalance() const settings = useSettingsState(); const query = useQueryState() @@ -169,6 +171,9 @@ export default function Form() { pollFee(!value) setShowSwapModal(value) value && swap?.id ? setSwapPath(swap?.id, router) : removeSwapPath(router) + if (value === false && swap?.source_network) { + fetchBalance(swap?.source_network, swap?.source_token) + } }, [router, swap]) return handleShowSwapModal(false)}> diff --git a/hooks/useBalance.ts b/hooks/useBalance.ts index ff8ff6978..54807ab0a 100644 --- a/hooks/useBalance.ts +++ b/hooks/useBalance.ts @@ -88,7 +88,6 @@ export default function useBalanceProvider() { && address) { try { setIsBalanceLoading(true) - debugger const provider = getBalanceProvider(network) const newBalance = await provider?.getBalance({ diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03dc..a4a7b3f5c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.