From 7e864e0381820dd3b6eb3f2be19db8f6ec3ff29f Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 8 Aug 2024 12:37:47 +0700 Subject: [PATCH 01/16] fix: use ether balance dedupe --- .../stake-form-context/stake-form-context.tsx | 10 +-- features/wsteth/shared/wallet/wallet.tsx | 6 +- .../wrap/hooks/use-wrap-form-network-data.ts | 12 +--- package.json | 4 +- shared/hooks/use-balance.ts | 29 ++++++++ shared/wallet/button/button.tsx | 12 ++-- yarn.lock | 68 ++++++++++++------- 7 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 shared/hooks/use-balance.ts diff --git a/features/stake/stake-form/stake-form-context/stake-form-context.tsx b/features/stake/stake-form/stake-form-context/stake-form-context.tsx index 0a0d6dd1c..f101ed140 100644 --- a/features/stake/stake-form/stake-form-context/stake-form-context.tsx +++ b/features/stake/stake-form/stake-form-context/stake-form-context.tsx @@ -11,7 +11,7 @@ import { import { useForm, FormProvider } from 'react-hook-form'; import { useRouter } from 'next/router'; -import { useEthereumBalance, useSTETHBalance } from '@lido-sdk/react'; +import { useSTETHBalance } from '@lido-sdk/react'; import { parseEther } from '@ethersproject/units'; import { @@ -39,6 +39,7 @@ import { import { useStake } from '../use-stake'; import { useStethSubmitGasLimit } from '../hooks'; +import { useEthereumBalance } from 'shared/hooks/use-balance'; // // Data context @@ -75,9 +76,10 @@ const useStakeFormNetworkData = (): StakeFormNetworkData => { const { data: etherBalance, - update: updateEtherBalance, - initialLoading: isEtherBalanceLoading, - } = useEthereumBalance(undefined, STRATEGY_LAZY); + refetch: updateEtherBalance, + isLoading: isEtherBalanceLoading, + } = useEthereumBalance(); + const { data: stakingLimitInfo, mutate: mutateStakeLimit, diff --git a/features/wsteth/shared/wallet/wallet.tsx b/features/wsteth/shared/wallet/wallet.tsx index 9f4b133f0..026084e46 100644 --- a/features/wsteth/shared/wallet/wallet.tsx +++ b/features/wsteth/shared/wallet/wallet.tsx @@ -4,7 +4,6 @@ import { Divider, Text } from '@lidofinance/lido-ui'; import { TOKENS } from '@lido-sdk/constants'; import { useSDK, - useEthereumBalance, useSTETHBalance, useWSTETHBalance, useTokenAddress, @@ -28,10 +27,11 @@ import { import { overrideWithQAMockBoolean } from 'utils/qa'; import { StyledCard } from './styles'; +import { useEthereumBalance } from 'shared/hooks/use-balance'; const WalletComponent: WalletComponentType = (props) => { const { account } = useSDK(); - const ethBalance = useEthereumBalance(undefined, STRATEGY_LAZY); + const ethBalance = useEthereumBalance(); const stethBalance = useSTETHBalance(STRATEGY_LAZY); const wstethBalance = useWSTETHBalance(STRATEGY_LAZY); @@ -46,7 +46,7 @@ const WalletComponent: WalletComponentType = (props) => { { const { isMultisig, isLoading: isMultisigLoading } = useIsMultisig(); - const { data: ethBalance, update: ethBalanceUpdate } = useEthereumBalance( - undefined, - STRATEGY_LAZY, - ); + const { data: ethBalance, refetch: ethBalanceUpdate } = useEthereumBalance(); const { data: stethBalance, update: stethBalanceUpdate } = useSTETHBalance(STRATEGY_LAZY); const { data: wstethBalance, update: wstethBalanceUpdate } = diff --git a/package.json b/package.json index 52a8523f8..2952a1dc3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@lidofinance/next-pages": "^0.41.0", "@lidofinance/rpc": "^0.41.0", "@lidofinance/satanizer": "^0.41.0", - "@tanstack/react-query": "^5.48.0", + "@tanstack/react-query": "^5.51.21", "@wagmi/core": "^2.11.6", "bignumber.js": "9.1.0", "copy-to-clipboard": "^3.3.1", @@ -79,7 +79,7 @@ "tiny-async-pool": "^1.2.0", "tiny-invariant": "^1.1.0", "uuid": "^8.3.2", - "viem": "2.13.3", + "viem": "2.18.8", "wagmi": "2.12.2" }, "devDependencies": { diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts new file mode 100644 index 000000000..8b0dd8ae2 --- /dev/null +++ b/shared/hooks/use-balance.ts @@ -0,0 +1,29 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { BigNumber } from 'ethers'; +import { useEffect } from 'react'; +import { useBlockNumber, useBalance, useAccount } from 'wagmi'; +import type { GetBalanceData } from 'wagmi/query'; + +const dataToBN = (data: GetBalanceData) => + BigNumber.from(data.value.toString()); + +export const useEthereumBalance = () => { + const queryClient = useQueryClient(); + const { address } = useAccount(); + const { data: blockNumber } = useBlockNumber({ watch: true }); + const queryData = useBalance({ + address, + query: { select: dataToBN, staleTime: 7000, enabled: !!address }, + }); + + useEffect(() => { + void queryClient.invalidateQueries( + { queryKey: queryData.queryKey }, + // this tells RQ to not force another refetch if this query is already revalidating + // dedups rpc requests + { cancelRefetch: false }, + ); + }, [blockNumber, queryClient, queryData.queryKey]); + + return queryData; +}; diff --git a/shared/wallet/button/button.tsx b/shared/wallet/button/button.tsx index 17f80c380..7d6e9a451 100644 --- a/shared/wallet/button/button.tsx +++ b/shared/wallet/button/button.tsx @@ -1,9 +1,7 @@ import { FC } from 'react'; import { useAccount } from 'wagmi'; import { ButtonProps, useBreakpoint } from '@lidofinance/lido-ui'; -import { useEthereumBalance } from '@lido-sdk/react'; -import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { FormatToken } from 'shared/formatters'; import { useDappStatus } from 'shared/hooks/use-dapp-status'; @@ -16,6 +14,7 @@ import { WalledButtonBalanceStyle, WalledButtonLoaderStyle, } from './styles'; +import { useEthereumBalance } from 'shared/hooks/use-balance'; export const Button: FC = (props) => { const { onClick, ...rest } = props; @@ -25,10 +24,7 @@ export const Button: FC = (props) => { const { isDappActive } = useDappStatus(); const { openModal } = useWalletModal(); - const { data: balance, initialLoading } = useEthereumBalance( - undefined, - STRATEGY_LAZY, - ); + const { data: balance, isLoading } = useEthereumBalance(); return ( = (props) => { variant="text" color="secondary" onClick={() => openModal({})} - $isAddPaddingLeft={!initialLoading && !isDappActive && !isMobile} + $isAddPaddingLeft={!isLoading && !isDappActive && !isMobile} {...rest} > - {initialLoading ? ( + {isLoading ? ( ) : ( isDappActive && ( diff --git a/yarn.lock b/yarn.lock index 9f1bb5b2a..ea9ccd246 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1005,6 +1005,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== +"@babel/runtime@^7.12.5": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.19.4", "@babel/runtime@^7.21.0": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" @@ -2594,12 +2601,19 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@^1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== + dependencies: + "@noble/hashes" "1.4.0" + "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@~1.4.0": +"@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== @@ -3342,17 +3356,17 @@ dependencies: tslib "^2.4.0" -"@tanstack/query-core@5.48.0": - version "5.48.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.48.0.tgz#a3308ec925d8c16d64c789899d6c084c2fe30cbc" - integrity sha512-lZAfPPeVIqXCswE9SSbG33B6/91XOWt/Iq41bFeWb/mnHwQSIfFRbkS4bfs+WhIk9abRArF9Id2fp0Mgo+hq6Q== +"@tanstack/query-core@5.51.21": + version "5.51.21" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.51.21.tgz#a510469c6c30d3de2a8b8798e340169a4b0fd08f" + integrity sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw== -"@tanstack/react-query@^5.48.0": - version "5.48.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.48.0.tgz#7890620272b48aeb278498dfe082f27518f3ac6d" - integrity sha512-GDExbjYWzvDokyRqMSWXdrPiYpp95Aig0oeMIrxTaruOJJgWiWfUP//OAaowm2RrRkGVsavSZdko/XmIrrV2Nw== +"@tanstack/react-query@^5.51.21": + version "5.51.21" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.51.21.tgz#cdd14677bcc809a83e01b6c38842c841ce7420af" + integrity sha512-Q/V81x3sAYgCsxjwOkfLXfrmoG+FmDhLeHH5okC/Bp8Aaw2c33lbEo/mMcMnkxUPVtB2FLpzHT0tq3c+OlZEbw== dependencies: - "@tanstack/query-core" "5.48.0" + "@tanstack/query-core" "5.51.21" "@trysound/sax@0.2.0": version "0.2.0" @@ -4128,11 +4142,6 @@ abitype@0.9.8: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== -abitype@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" - integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== - abitype@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" @@ -10948,19 +10957,20 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -viem@2.13.3: - version "2.13.3" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.13.3.tgz#950426e4cacf5e12fab2c202a339371901712481" - integrity sha512-3tlwDRKHSelupFjbFMdUxF41f79ktyH2F9PAQ9Dltbs1DpdDlR1x+Ksa0th6qkyjjAbpDZP3F5nMTJv/1GVPdQ== +viem@2.18.8: + version "2.18.8" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.18.8.tgz#5e65050ddc3bdc0f928c0ca22b33bc720bf36639" + integrity sha512-Fi5d9fd/LBiVtJ5eV2c99yrdt4dJH5Vbkf2JajwCqHYuV4ErSk/sm+L6Ru3rzT67rfRHSOQibTZxByEBua/WLw== dependencies: "@adraffy/ens-normalize" "1.10.0" - "@noble/curves" "1.2.0" - "@noble/hashes" "1.3.2" - "@scure/bip32" "1.3.2" - "@scure/bip39" "1.2.1" - abitype "1.0.0" + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + abitype "1.0.5" isows "1.0.4" - ws "8.13.0" + webauthn-p256 "0.0.5" + ws "8.17.1" viem@^1.1.4: version "1.21.4" @@ -11006,6 +11016,14 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +webauthn-p256@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" + integrity sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg== + dependencies: + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + "webextension-polyfill@>=0.10.0 <1.0": version "0.12.0" resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz#f62c57d2cd42524e9fbdcee494c034cae34a3d69" From 448ed06eead34ae66ca955df1b275f927bad5a5c Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 8 Aug 2024 15:16:34 +0700 Subject: [PATCH 02/16] feat: custom viem web3 transport --- providers/web3.tsx | 31 ++++++---- shared/hooks/use-balance.ts | 2 +- utils/use-web3-transport.ts | 109 ++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 utils/use-web3-transport.ts diff --git a/providers/web3.tsx b/providers/web3.tsx index c13000bae..92b3f6b48 100644 --- a/providers/web3.tsx +++ b/providers/web3.tsx @@ -1,6 +1,6 @@ -import { FC, PropsWithChildren, useMemo } from 'react'; +import { FC, PropsWithChildren, useEffect, useMemo } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { http, WagmiProvider, createConfig } from 'wagmi'; +import { WagmiProvider, createConfig, useConnections } from 'wagmi'; import * as wagmiChains from 'wagmi/chains'; import { AutoConnect, @@ -16,6 +16,7 @@ import { CHAINS } from 'consts/chains'; import { ConnectWalletModal } from 'shared/wallet/connect-wallet-modal'; import { SDKLegacyProvider } from './sdk-legacy'; +import { useWeb3Transport } from 'utils/use-web3-transport'; type ChainsList = [wagmiChains.Chain, ...wagmiChains.Chain[]]; @@ -63,15 +64,21 @@ const Web3Provider: FC = ({ children }) => { return getWalletsDataList({ walletsList: WalletsListEthereum, rpc: backendRPC, - walletconnectProjectId: walletconnectProjectId, - defaultChain: defaultChain, + walletconnectProjectId, + defaultChain, }); }, [backendRPC, defaultChain, walletconnectProjectId]); + const { transportMap, onActiveConnection } = useWeb3Transport( + supportedChains, + backendRPC, + ); + const wagmiConfig = useMemo(() => { return createConfig({ chains: supportedChains, ssr: true, + connectors: [], batch: { // eth_call's will be batched via multicall contract every 100ms multicall: { @@ -80,15 +87,15 @@ const Web3Provider: FC = ({ children }) => { }, multiInjectedProviderDiscovery: false, pollingInterval: config.PROVIDER_POLLING_INTERVAL, - transports: supportedChains.reduce( - (res, curr) => ({ - ...res, - [curr.id]: http(backendRPC[curr.id], { batch: true }), - }), - {}, - ), + transports: transportMap, }); - }, [supportedChains, backendRPC]); + }, [supportedChains, transportMap]); + + const [activeConnection] = useConnections({ config: wagmiConfig }); + + useEffect(() => { + void onActiveConnection(activeConnection ?? null); + }, [activeConnection, onActiveConnection]); return ( // default wagmi autoConnect, MUST be false in our case, because we use custom autoConnect from Reef Knot diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 8b0dd8ae2..2b53e84d1 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -10,7 +10,7 @@ const dataToBN = (data: GetBalanceData) => export const useEthereumBalance = () => { const queryClient = useQueryClient(); const { address } = useAccount(); - const { data: blockNumber } = useBlockNumber({ watch: true }); + const { data: blockNumber } = useBlockNumber({ watch: !!address }); const queryData = useBalance({ address, query: { select: dataToBN, staleTime: 7000, enabled: !!address }, diff --git a/utils/use-web3-transport.ts b/utils/use-web3-transport.ts new file mode 100644 index 000000000..2cd7761a9 --- /dev/null +++ b/utils/use-web3-transport.ts @@ -0,0 +1,109 @@ +// TODO: move this to dedicated web3 configuration module + +import { useMemo, useCallback } from 'react'; +import { + Transport, + fallback, + createTransport, + http, + EIP1193Provider, + custom, + Chain, +} from 'viem'; +import { Connection } from 'wagmi'; + +// Viem transport wrapper that allows runtime changes via setter +const runtimeMutableTransport = ( + mainTransports: Transport[], +): [Transport, (t: Transport | null) => void] => { + let withInjectedTransport: Transport | null = null; + return [ + (params) => { + const defaultTransport = fallback(mainTransports)(params); + + return createTransport( + { + key: 'RuntimeMutableTransport', + name: 'RuntimeMutableTransport', + //@ts-expect-error invalid typings + async request(requestParams, options) { + const transport = withInjectedTransport + ? withInjectedTransport(params) + : defaultTransport; + return transport.request(requestParams, options); + }, + type: 'fallback', + }, + { + transports: defaultTransport.value?.transports, + }, + ); + }, + (injectedTransport: Transport | null) => { + if (injectedTransport) { + withInjectedTransport = fallback([ + injectedTransport, + ...mainTransports, + ]); + } else { + withInjectedTransport = null; + } + }, + ]; +}; + +// returns Viem transport map that uses browser wallet RPC provider when avaliable fallbacked by our RPC +export const useWeb3Transport = ( + supportedChains: Chain[], + backendRpcMap: Record, +) => { + const { transportMap, setTransportMap } = useMemo(() => { + return supportedChains.reduce( + ({ transportMap, setTransportMap }, chain) => { + const [transport, setTransport] = runtimeMutableTransport([ + http(backendRpcMap[chain.id], { + batch: true, + name: backendRpcMap[chain.id], + }), + http(), + ]); + return { + transportMap: { + ...transportMap, + [chain.id]: transport, + }, + setTransportMap: { + ...setTransportMap, + [chain.id]: setTransport, + }, + }; + }, + { + transportMap: {} as Record, + setTransportMap: {} as Record void>, + }, + ); + }, [supportedChains, backendRpcMap]); + + const onActiveConnection = useCallback( + async (activeConnection: Connection | null) => { + for (const chain of supportedChains) { + const setTransport = setTransportMap[chain.id]; + if ( + activeConnection && + chain.id === activeConnection.chainId && + activeConnection.connector.type === 'injected' + ) { + const provider = (await activeConnection.connector?.getProvider?.({ + chainId: chain.id, + })) as EIP1193Provider | undefined; + + setTransport(provider ? custom(provider) : null); + } else setTransport(null); + } + }, + [setTransportMap, supportedChains], + ); + + return { transportMap, onActiveConnection }; +}; From b4610f135805a83018854706da39dd01ba91aee3 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 8 Aug 2024 17:39:56 +0700 Subject: [PATCH 03/16] feat: use token balance from sdk --- .../rewardsListContent/RewardsListContent.tsx | 7 +- .../stake-form-context/stake-form-context.tsx | 10 +- .../use-request-form-data-context-value.ts | 20 ++-- features/wsteth/shared/wallet/wallet.tsx | 22 ++-- .../hooks/use-unwrap-form-network-data.ts | 10 +- .../wrap/hooks/use-wrap-form-network-data.ts | 15 +-- package.json | 1 + providers/index.tsx | 13 ++- providers/lido-sdk.tsx | 45 ++++++++ shared/hooks/use-balance.ts | 103 +++++++++++++++++- yarn.lock | 45 +++++++- 11 files changed, 236 insertions(+), 55 deletions(-) create mode 100644 providers/lido-sdk.tsx diff --git a/features/rewards/components/rewardsListContent/RewardsListContent.tsx b/features/rewards/components/rewardsListContent/RewardsListContent.tsx index ff5d9de6e..1bce218c7 100644 --- a/features/rewards/components/rewardsListContent/RewardsListContent.tsx +++ b/features/rewards/components/rewardsListContent/RewardsListContent.tsx @@ -1,13 +1,12 @@ import { FC } from 'react'; import { Loader, Divider } from '@lidofinance/lido-ui'; -import { useSTETHBalance } from '@lido-sdk/react'; import { Zero } from '@ethersproject/constants'; -import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { useRewardsHistory } from 'features/rewards/hooks'; import { ErrorBlockNoSteth } from 'features/rewards/components/errorBlocks/ErrorBlockNoSteth'; import { RewardsTable } from 'features/rewards/components/rewardsTable'; import { useDappStatus } from 'shared/hooks/use-dapp-status'; +import { useStethBalance } from 'shared/hooks/use-balance'; import { RewardsListsEmpty } from './RewardsListsEmpty'; import { RewardsListErrorMessage } from './RewardsListErrorMessage'; @@ -28,8 +27,8 @@ export const RewardsListContent: FC = () => { setPage, isLagging, } = useRewardsHistory(); - const { data: stethBalance, initialLoading: isStethBalanceLoading } = - useSTETHBalance(STRATEGY_LAZY); + const { data: stethBalance, isLoading: isStethBalanceLoading } = + useStethBalance(); const hasSteth = stethBalance?.gt(Zero); if (!isDappActive || (!data && !initialLoading && !error)) diff --git a/features/stake/stake-form/stake-form-context/stake-form-context.tsx b/features/stake/stake-form/stake-form-context/stake-form-context.tsx index f101ed140..5db80bf76 100644 --- a/features/stake/stake-form/stake-form-context/stake-form-context.tsx +++ b/features/stake/stake-form/stake-form-context/stake-form-context.tsx @@ -11,7 +11,6 @@ import { import { useForm, FormProvider } from 'react-hook-form'; import { useRouter } from 'next/router'; -import { useSTETHBalance } from '@lido-sdk/react'; import { parseEther } from '@ethersproject/units'; import { @@ -24,7 +23,6 @@ import { useMaxGasPrice } from 'shared/hooks'; import { useIsMultisig } from 'shared/hooks/useIsMultisig'; import { useFormControllerRetry } from 'shared/hook-form/form-controller/use-form-controller-retry-delegate'; -import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { config } from 'config'; import { @@ -39,7 +37,7 @@ import { import { useStake } from '../use-stake'; import { useStethSubmitGasLimit } from '../hooks'; -import { useEthereumBalance } from 'shared/hooks/use-balance'; +import { useEthereumBalance, useStethBalance } from 'shared/hooks/use-balance'; // // Data context @@ -61,9 +59,9 @@ export const useStakeFormData = () => { const useStakeFormNetworkData = (): StakeFormNetworkData => { const { data: stethBalance, - update: updateStethBalance, - initialLoading: isStethBalanceLoading, - } = useSTETHBalance(STRATEGY_LAZY); + refetch: updateStethBalance, + isLoading: isStethBalanceLoading, + } = useStethBalance(); const { isMultisig, isLoading: isMultisigLoading } = useIsMultisig(); const gasLimit = useStethSubmitGasLimit(); const { maxGasPrice, initialLoading: isMaxGasPriceLoading } = diff --git a/features/withdrawals/request/request-form-context/use-request-form-data-context-value.ts b/features/withdrawals/request/request-form-context/use-request-form-data-context-value.ts index 2d88b5683..86bddb7ac 100644 --- a/features/withdrawals/request/request-form-context/use-request-form-data-context-value.ts +++ b/features/withdrawals/request/request-form-context/use-request-form-data-context-value.ts @@ -1,14 +1,10 @@ -import { - useSTETHContractRPC, - useSTETHBalance, - useWSTETHBalance, - useContractSWR, -} from '@lido-sdk/react'; +import { useSTETHContractRPC, useContractSWR } from '@lido-sdk/react'; import { useClaimData } from 'features/withdrawals/contexts/claim-data-context'; import { useWithdrawals } from 'features/withdrawals/contexts/withdrawals-context'; import { useUnfinalizedStETH } from 'features/withdrawals/hooks'; import { useCallback, useMemo } from 'react'; import { useWstethBySteth } from 'shared/hooks'; +import { useStethBalance, useWstethBalance } from 'shared/hooks/use-balance'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; // Provides all data fetching for form to function @@ -28,14 +24,14 @@ export const useRequestFormDataContextValue = () => { } = useWithdrawals(); const { data: balanceSteth, - update: stethUpdate, - initialLoading: isStethBalanceLoading, - } = useSTETHBalance(STRATEGY_LAZY); + refetch: stethUpdate, + isLoading: isStethBalanceLoading, + } = useStethBalance(); const { data: balanceWSteth, - update: wstethUpdate, - initialLoading: isWstethBalanceLoading, - } = useWSTETHBalance(STRATEGY_LAZY); + refetch: wstethUpdate, + isLoading: isWstethBalanceLoading, + } = useWstethBalance(); const { data: unfinalizedStETH, update: unfinalizedStETHUpdate, diff --git a/features/wsteth/shared/wallet/wallet.tsx b/features/wsteth/shared/wallet/wallet.tsx index 026084e46..a5cea6a8e 100644 --- a/features/wsteth/shared/wallet/wallet.tsx +++ b/features/wsteth/shared/wallet/wallet.tsx @@ -2,16 +2,10 @@ import { memo } from 'react'; import { Divider, Text } from '@lidofinance/lido-ui'; import { TOKENS } from '@lido-sdk/constants'; -import { - useSDK, - useSTETHBalance, - useWSTETHBalance, - useTokenAddress, -} from '@lido-sdk/react'; +import { useSDK, useTokenAddress } from '@lido-sdk/react'; import { useConfig } from 'config'; import { CHAINS } from 'consts/chains'; -import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { FormatToken } from 'shared/formatters'; import { TokenToWallet } from 'shared/components'; import { useWstethBySteth, useStethByWsteth } from 'shared/hooks'; @@ -27,13 +21,17 @@ import { import { overrideWithQAMockBoolean } from 'utils/qa'; import { StyledCard } from './styles'; -import { useEthereumBalance } from 'shared/hooks/use-balance'; +import { + useEthereumBalance, + useStethBalance, + useWstethBalance, +} from 'shared/hooks/use-balance'; const WalletComponent: WalletComponentType = (props) => { const { account } = useSDK(); const ethBalance = useEthereumBalance(); - const stethBalance = useSTETHBalance(STRATEGY_LAZY); - const wstethBalance = useWSTETHBalance(STRATEGY_LAZY); + const stethBalance = useStethBalance(); + const wstethBalance = useWstethBalance(); const stethAddress = useTokenAddress(TOKENS.STETH); const wstethAddress = useTokenAddress(TOKENS.WSTETH); @@ -62,7 +60,7 @@ const WalletComponent: WalletComponentType = (props) => { { { const { isMultisig } = useIsMultisig(); - const { data: stethBalance, update: stethBalanceUpdate } = - useSTETHBalance(STRATEGY_LAZY); - const { data: wstethBalance, update: wstethBalanceUpdate } = - useWSTETHBalance(STRATEGY_LAZY); + const { data: stethBalance, refetch: stethBalanceUpdate } = useStethBalance(); + const { data: wstethBalance, refetch: wstethBalanceUpdate } = + useWstethBalance(); const revalidateUnwrapFormData = useCallback(async () => { await Promise.allSettled([stethBalanceUpdate(), wstethBalanceUpdate()]); diff --git a/features/wsteth/wrap/hooks/use-wrap-form-network-data.ts b/features/wsteth/wrap/hooks/use-wrap-form-network-data.ts index 5ab0d38f0..b0da4caf7 100644 --- a/features/wsteth/wrap/hooks/use-wrap-form-network-data.ts +++ b/features/wsteth/wrap/hooks/use-wrap-form-network-data.ts @@ -1,23 +1,24 @@ import { useCallback, useMemo } from 'react'; -import { useWSTETHBalance, useSTETHBalance } from '@lido-sdk/react'; import { config } from 'config'; -import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { useIsMultisig } from 'shared/hooks/useIsMultisig'; import { useTokenMaxAmount } from 'shared/hooks/use-token-max-amount'; import { useMaxGasPrice, useStakingLimitInfo } from 'shared/hooks'; import { useWrapGasLimit } from './use-wrap-gas-limit'; -import { useEthereumBalance } from 'shared/hooks/use-balance'; +import { + useEthereumBalance, + useStethBalance, + useWstethBalance, +} from 'shared/hooks/use-balance'; // Provides all data fetching for form to function export const useWrapFormNetworkData = () => { const { isMultisig, isLoading: isMultisigLoading } = useIsMultisig(); const { data: ethBalance, refetch: ethBalanceUpdate } = useEthereumBalance(); - const { data: stethBalance, update: stethBalanceUpdate } = - useSTETHBalance(STRATEGY_LAZY); - const { data: wstethBalance, update: wstethBalanceUpdate } = - useWSTETHBalance(STRATEGY_LAZY); + const { data: stethBalance, refetch: stethBalanceUpdate } = useStethBalance(); + const { data: wstethBalance, refetch: wstethBalanceUpdate } = + useWstethBalance(); const { data: stakeLimitInfo, mutate: stakeLimitInfoUpdate } = useStakingLimitInfo(); diff --git a/package.json b/package.json index 2952a1dc3..13e9ae7f1 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@lidofinance/api-rpc": "^0.41.0", "@lidofinance/eth-api-providers": "^0.41.0", "@lidofinance/eth-providers": "^0.41.0", + "@lidofinance/lido-ethereum-sdk": "^3.3.1", "@lidofinance/lido-ui": "^3.26.0", "@lidofinance/next-api-wrapper": "^0.41.0", "@lidofinance/next-ip-rate-limit": "^0.41.0", diff --git a/providers/index.tsx b/providers/index.tsx index 5da5a1bdc..5d6e2c72a 100644 --- a/providers/index.tsx +++ b/providers/index.tsx @@ -9,6 +9,7 @@ import { IPFSInfoBoxStatusesProvider } from './ipfs-info-box-statuses'; import { InpageNavigationProvider } from './inpage-navigation'; import { ModalProvider } from './modal-provider'; import Web3Provider from './web3'; +import { LidoSDKProvider } from './lido-sdk'; export const Providers: FC = ({ children }) => ( @@ -16,11 +17,13 @@ export const Providers: FC = ({ children }) => ( - - - {children} - - + + + + {children} + + + diff --git a/providers/lido-sdk.tsx b/providers/lido-sdk.tsx new file mode 100644 index 000000000..b0c50e82a --- /dev/null +++ b/providers/lido-sdk.tsx @@ -0,0 +1,45 @@ +import { createContext, useContext, useMemo } from 'react'; +import { LidoSDKCore } from '@lidofinance/lido-ethereum-sdk/core'; +import { + LidoSDKstETH, + LidoSDKwstETH, +} from '@lidofinance/lido-ethereum-sdk/erc20'; +import invariant from 'tiny-invariant'; +import { useChainId, useClient, useConnectorClient } from 'wagmi'; + +type LidoSDKContextValue = { + core: LidoSDKCore; + steth: LidoSDKstETH; + wsteth: LidoSDKwstETH; +}; + +const LidoSDKContext = createContext(null); +LidoSDKContext.displayName = 'LidoSDKContext'; + +export const useLidoSDK = () => { + const value = useContext(LidoSDKContext); + invariant(value, 'useLidoSDK was used outside of LidoSDKProvider'); + return value; +}; + +export const LidoSDKProvider = ({ children }: React.PropsWithChildren) => { + const publicClient = useClient(); + const chainId = useChainId(); + const { data: walletClient } = useConnectorClient(); + const sdk = useMemo(() => { + const core = new LidoSDKCore({ + chainId, + logMode: 'none', + rpcProvider: publicClient as any, + web3Provider: walletClient as any, + }); + + const steth = new LidoSDKstETH({ core }); + const wsteth = new LidoSDKwstETH({ core }); + + return { core, steth, wsteth }; + }, [chainId, publicClient, walletClient]); + return ( + {children} + ); +}; diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 2b53e84d1..6ff0a4665 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -1,12 +1,28 @@ -import { useQueryClient } from '@tanstack/react-query'; +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { BigNumber } from 'ethers'; +import { useLidoSDK } from 'providers/lido-sdk'; import { useEffect } from 'react'; -import { useBlockNumber, useBalance, useAccount } from 'wagmi'; +import { + useBlockNumber, + useBalance, + useAccount, + useReadContract, + useWatchContractEvent, +} from 'wagmi'; + +import type { AbstractLidoSDKErc20 } from '@lidofinance/lido-ethereum-sdk/erc20'; + +import {} from '@lidofinance/lido-ethereum-sdk/erc20'; + import type { GetBalanceData } from 'wagmi/query'; +import { Address } from 'viem'; const dataToBN = (data: GetBalanceData) => BigNumber.from(data.value.toString()); +const NativeToBN = (data: bigint) => BigNumber.from(data.toString()); + export const useEthereumBalance = () => { const queryClient = useQueryClient(); const { address } = useAccount(); @@ -27,3 +43,86 @@ export const useEthereumBalance = () => { return queryData; }; + +type TokenContract = Awaited< + ReturnType['getContract']> +>; + +const useTokenBalance = (contract: TokenContract, address?: Address) => { + const queryClient = useQueryClient(); + const balanceQuery = useReadContract({ + abi: contract?.abi, + address: contract?.address, + functionName: 'balanceOf', + args: address && [address], + query: { enabled: !!address, select: NativeToBN }, + }); + + useWatchContractEvent({ + abi: contract?.abi, + address: contract?.address, + eventName: 'Transfer', + enabled: !!(address && balanceQuery.data), + args: { from: address! }, + onLogs: () => { + void queryClient.invalidateQueries( + { queryKey: balanceQuery.queryKey }, + { cancelRefetch: false }, + ); + }, + }); + + useWatchContractEvent({ + abi: contract?.abi, + address: contract?.address, + eventName: 'Transfer', + enabled: !!(address && balanceQuery.data), + args: { to: address! }, + onLogs: () => { + void queryClient.invalidateQueries( + { queryKey: balanceQuery.queryKey }, + { cancelRefetch: false }, + ); + }, + }); + + return balanceQuery; +}; + +export const useStethBalance = () => { + const { address } = useAccount(); + + const { steth, core } = useLidoSDK(); + + const { data: contractData, isLoading } = useQuery({ + queryKey: ['steth-contract', core.chainId], + enabled: !!address, + staleTime: Infinity, + queryFn: async () => steth.getContract(), + }); + + const contract = contractData as NonNullable; + + const balanceData = useTokenBalance(contract, address); + + return { ...balanceData, isLoading: isLoading || balanceData.isLoading }; +}; + +export const useWstethBalance = () => { + const { address } = useAccount(); + + const { wsteth, core } = useLidoSDK(); + + const { data: contractData, isLoading } = useQuery({ + queryKey: ['wsteth-contract', core.chainId], + enabled: !!address, + staleTime: Infinity, + queryFn: async () => wsteth.getContract(), + }); + + const contract = contractData as NonNullable; + + const balanceData = useTokenBalance(contract, address); + + return { ...balanceData, isLoading: isLoading || balanceData.isLoading }; +}; diff --git a/yarn.lock b/yarn.lock index ea9ccd246..c5e13a8c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1715,6 +1715,11 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@graphql-typed-document-node/core@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" + integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -2200,6 +2205,16 @@ resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.41.0.tgz#31e8c4abbc22375ea0b1eb54a34eee72f175e906" integrity sha512-qz/+C1J6soJjtrAOBIlfTdez60lK9lS8jqvdUSEGPHMr88Pg+6p77Zh5kzQynGXuwMeg7xKVqB5uMLFJPAOGGA== +"@lidofinance/lido-ethereum-sdk@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@lidofinance/lido-ethereum-sdk/-/lido-ethereum-sdk-3.3.1.tgz#5daabb594dd17e9e1af61ca0213410b7132232c1" + integrity sha512-lBnP5zHDUyOK0p9YS6MLp04oVgYo9TNd+mrvjJLFzrXl3UrrucRGkKr5Rq1qBEvT6D1AF6+8zv2BbCDdP2Eksw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + graphql "^16.8.1" + graphql-request "^6.1.0" + viem "^2.0.6" + "@lidofinance/lido-ui@^3.18.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lidofinance/lido-ui/-/lido-ui-3.21.0.tgz#da772e44ca7e96a062187858fe896ebc7e3d5cd1" @@ -5173,7 +5188,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-fetch@^3.1.4: +cross-fetch@^3.1.4, cross-fetch@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== @@ -6576,6 +6591,19 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f" + integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw== + dependencies: + "@graphql-typed-document-node/core" "^3.2.0" + cross-fetch "^3.1.5" + +graphql@^16.8.1: + version "16.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" + integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== + gray-matter@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" @@ -10986,6 +11014,21 @@ viem@^1.1.4: isows "1.0.3" ws "8.13.0" +viem@^2.0.6: + version "2.19.2" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.19.2.tgz#11f03621fd0d0d742f04e3da30fa49093a3cf612" + integrity sha512-BrR7fEEpuu9Om7obQGThb4BEu00PPHPKaUx+snB/F6yBZtr34FdXCPnphr+S73W2iIu/mt3yaRkfkLlD6a1R5g== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + abitype "1.0.5" + isows "1.0.4" + webauthn-p256 "0.0.5" + ws "8.17.1" + viem@^2.1.1: version "2.17.9" resolved "https://registry.yarnpkg.com/viem/-/viem-2.17.9.tgz#40ffd00a31621c8efdc4d49a58d5d30dc2d38d83" From 686897f64967e1659198dac52c4d5b7eb657e8f6 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 8 Aug 2024 17:50:50 +0700 Subject: [PATCH 04/16] fix: use polling --- shared/hooks/use-balance.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 6ff0a4665..c3817301d 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -13,8 +13,6 @@ import { import type { AbstractLidoSDKErc20 } from '@lidofinance/lido-ethereum-sdk/erc20'; -import {} from '@lidofinance/lido-ethereum-sdk/erc20'; - import type { GetBalanceData } from 'wagmi/query'; import { Address } from 'viem'; @@ -62,6 +60,8 @@ const useTokenBalance = (contract: TokenContract, address?: Address) => { abi: contract?.abi, address: contract?.address, eventName: 'Transfer', + poll: true, + enabled: !!(address && balanceQuery.data), args: { from: address! }, onLogs: () => { @@ -76,6 +76,7 @@ const useTokenBalance = (contract: TokenContract, address?: Address) => { abi: contract?.abi, address: contract?.address, eventName: 'Transfer', + poll: true, enabled: !!(address && balanceQuery.data), args: { to: address! }, onLogs: () => { From 95ce4569ea4171549ebd0c89a9b677cb86b9d2c1 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 9 Aug 2024 16:46:00 +0700 Subject: [PATCH 05/16] feat: use united token events subscription --- config/groups/web3.ts | 2 + pages/api/rpc.ts | 3 + providers/lido-sdk.tsx | 8 +- providers/web3.tsx | 5 +- shared/hooks/use-balance.ts | 183 +++++++++++++++++++++++++++++------- utils/use-web3-transport.ts | 20 +++- 6 files changed, 180 insertions(+), 41 deletions(-) diff --git a/config/groups/web3.ts b/config/groups/web3.ts index 06f27f001..3e072920d 100644 --- a/config/groups/web3.ts +++ b/config/groups/web3.ts @@ -2,6 +2,8 @@ import { parseEther } from '@ethersproject/units'; // interval in ms for RPC event polling for token balance and tx updates export const PROVIDER_POLLING_INTERVAL = 12_000; +// how long in ms to wait for RPC batching(multicall and provider) +export const PROVIDER_BATCH_TIME = 150; // account for gas estimation // will always have >=0.001 ether, >=0.001 stETH, >=0.001 wstETH diff --git a/pages/api/rpc.ts b/pages/api/rpc.ts index 01eaa99d1..9e9daa157 100644 --- a/pages/api/rpc.ts +++ b/pages/api/rpc.ts @@ -43,6 +43,9 @@ const rpc = rpcFactory({ 'eth_getLogs', 'eth_chainId', 'net_version', + 'eth_newFilter', + 'eth_getFilterChanges', + 'eth_uninstallFilter', ], defaultChain: `${config.defaultChain}`, providers: { diff --git a/providers/lido-sdk.tsx b/providers/lido-sdk.tsx index b0c50e82a..ddd18a967 100644 --- a/providers/lido-sdk.tsx +++ b/providers/lido-sdk.tsx @@ -6,11 +6,13 @@ import { } from '@lidofinance/lido-ethereum-sdk/erc20'; import invariant from 'tiny-invariant'; import { useChainId, useClient, useConnectorClient } from 'wagmi'; +import { useTokenTransferSubscription } from 'shared/hooks/use-balance'; type LidoSDKContextValue = { core: LidoSDKCore; steth: LidoSDKstETH; wsteth: LidoSDKwstETH; + subscribeToTokenUpdates: ReturnType; }; const LidoSDKContext = createContext(null); @@ -23,9 +25,11 @@ export const useLidoSDK = () => { }; export const LidoSDKProvider = ({ children }: React.PropsWithChildren) => { + const subscribe = useTokenTransferSubscription(); const publicClient = useClient(); const chainId = useChainId(); const { data: walletClient } = useConnectorClient(); + const sdk = useMemo(() => { const core = new LidoSDKCore({ chainId, @@ -37,8 +41,8 @@ export const LidoSDKProvider = ({ children }: React.PropsWithChildren) => { const steth = new LidoSDKstETH({ core }); const wsteth = new LidoSDKwstETH({ core }); - return { core, steth, wsteth }; - }, [chainId, publicClient, walletClient]); + return { core, steth, wsteth, subscribeToTokenUpdates: subscribe }; + }, [chainId, publicClient, subscribe, walletClient]); return ( {children} ); diff --git a/providers/web3.tsx b/providers/web3.tsx index 92b3f6b48..5eeeccdd1 100644 --- a/providers/web3.tsx +++ b/providers/web3.tsx @@ -79,10 +79,11 @@ const Web3Provider: FC = ({ children }) => { chains: supportedChains, ssr: true, connectors: [], + batch: { - // eth_call's will be batched via multicall contract every 100ms + // eth_call's can be batched via multicall contract multicall: { - wait: 100, + wait: config.PROVIDER_BATCH_TIME, }, }, multiInjectedProviderDiscovery: false, diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index c3817301d..e1f9c9221 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { QueryKey, useQuery, useQueryClient } from '@tanstack/react-query'; import { BigNumber } from 'ethers'; import { useLidoSDK } from 'providers/lido-sdk'; -import { useEffect } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useBlockNumber, useBalance, @@ -12,14 +12,12 @@ import { } from 'wagmi'; import type { AbstractLidoSDKErc20 } from '@lidofinance/lido-ethereum-sdk/erc20'; - import type { GetBalanceData } from 'wagmi/query'; -import { Address } from 'viem'; +import type { Address, Log } from 'viem'; -const dataToBN = (data: GetBalanceData) => - BigNumber.from(data.value.toString()); +const nativeToBN = (data: bigint) => BigNumber.from(data.toString()); -const NativeToBN = (data: bigint) => BigNumber.from(data.toString()); +const balanceToBN = (data: GetBalanceData) => nativeToBN(data.value); export const useEthereumBalance = () => { const queryClient = useQueryClient(); @@ -27,7 +25,7 @@ export const useEthereumBalance = () => { const { data: blockNumber } = useBlockNumber({ watch: !!address }); const queryData = useBalance({ address, - query: { select: dataToBN, staleTime: 7000, enabled: !!address }, + query: { select: balanceToBN, staleTime: 7000, enabled: !!address }, }); useEffect(() => { @@ -46,47 +44,162 @@ type TokenContract = Awaited< ReturnType['getContract']> >; -const useTokenBalance = (contract: TokenContract, address?: Address) => { +type TokenSubscriptionState = Record< + Address, + { + subscribers: number; + queryKey: QueryKey; + } +>; + +type SubscribeArgs = { + address: Address; + queryKey: QueryKey; +}; + +export const Erc20EventsAbi = [ + { + type: 'event', + name: 'Approval', + inputs: [ + { indexed: true, name: 'owner', type: 'address' }, + { indexed: true, name: 'spender', type: 'address' }, + { indexed: false, name: 'value', type: 'uint256' }, + ], + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { indexed: true, name: 'from', type: 'address' }, + { indexed: true, name: 'to', type: 'address' }, + { indexed: false, name: 'value', type: 'uint256' }, + ], + }, +] as const; + +export const useTokenTransferSubscription = () => { + const { address } = useAccount(); const queryClient = useQueryClient(); - const balanceQuery = useReadContract({ - abi: contract?.abi, - address: contract?.address, - functionName: 'balanceOf', - args: address && [address], - query: { enabled: !!address, select: NativeToBN }, - }); + const [subscriptions, setSubscriptions] = useState( + {}, + ); + + const tokens = useMemo( + () => Object.keys(subscriptions) as Address[], + [subscriptions], + ); + + const onLogs = useCallback( + (logs: Log[]) => { + for (const log of logs) { + const subscription = subscriptions[log.address]; + if (subscription) { + // we could optimistically update balance data + // but it's easier to refetch balance after transfer + void queryClient.invalidateQueries( + { + queryKey: subscription.queryKey, + }, + { cancelRefetch: false }, + ); + } + } + }, + [queryClient, subscriptions], + ); + + const shouldWatch = address && tokens.length > 0; useWatchContractEvent({ - abi: contract?.abi, - address: contract?.address, + abi: Erc20EventsAbi, eventName: 'Transfer', + batch: true, poll: true, - - enabled: !!(address && balanceQuery.data), - args: { from: address! }, - onLogs: () => { - void queryClient.invalidateQueries( - { queryKey: balanceQuery.queryKey }, - { cancelRefetch: false }, - ); + args: { + to: address, }, + address: tokens, + enabled: shouldWatch, + onLogs, }); useWatchContractEvent({ - abi: contract?.abi, - address: contract?.address, + abi: Erc20EventsAbi, eventName: 'Transfer', + batch: true, poll: true, - enabled: !!(address && balanceQuery.data), - args: { to: address! }, - onLogs: () => { - void queryClient.invalidateQueries( - { queryKey: balanceQuery.queryKey }, - { cancelRefetch: false }, - ); + args: { + from: address, }, + address: tokens, + enabled: shouldWatch, + onLogs, + }); + + const subscribe = useCallback(({ address, queryKey }: SubscribeArgs) => { + setSubscriptions((old) => { + const existing = old[address]; + return { + ...old, + [address]: { + queryKey, + subscribers: existing?.subscribers ?? 0 + 1, + }, + }; + }); + + // unsubscribe + return () => { + setSubscriptions((old) => { + const existing = old[address]; + if (existing) { + if (existing.subscribers > 1) { + return { + ...old, + [address]: { + ...existing, + subscribers: existing.subscribers - 1, + }, + }; + } else { + delete old[address]; + return { ...old }; + } + } else return old; + }); + }; + }, []); + + return subscribe; +}; + +// NB: contract can be undefined but for better wagmi typings is casted as NoNNullable +const useTokenBalance = (contract: TokenContract, address?: Address) => { + const { subscribeToTokenUpdates } = useLidoSDK(); + // const queryClient = useQueryClient(); + const balanceQuery = useReadContract({ + abi: contract?.abi, + address: contract?.address, + functionName: 'balanceOf', + args: address && [address], + query: { enabled: !!address, select: nativeToBN }, }); + useEffect(() => { + if (address && contract?.address) { + return subscribeToTokenUpdates({ + address: contract.address, + queryKey: balanceQuery.queryKey, + }); + } + }, [ + address, + contract?.address, + balanceQuery.queryKey, + subscribeToTokenUpdates, + ]); + return balanceQuery; }; diff --git a/utils/use-web3-transport.ts b/utils/use-web3-transport.ts index 2cd7761a9..0b887e328 100644 --- a/utils/use-web3-transport.ts +++ b/utils/use-web3-transport.ts @@ -1,5 +1,6 @@ // TODO: move this to dedicated web3 configuration module +import { config } from 'config'; import { useMemo, useCallback } from 'react'; import { Transport, @@ -12,6 +13,14 @@ import { } from 'viem'; import { Connection } from 'wagmi'; +// We disable those methods so wagmi uses getLogs intestead to watch events +// Filters are not suitable for public rpc and break between fallbacks +const DISABLED_METHODS = new Set([ + 'eth_newFilter', + 'eth_getFilterChanges', + 'eth_uninstallFilter', +]); + // Viem transport wrapper that allows runtime changes via setter const runtimeMutableTransport = ( mainTransports: Transport[], @@ -27,6 +36,10 @@ const runtimeMutableTransport = ( name: 'RuntimeMutableTransport', //@ts-expect-error invalid typings async request(requestParams, options) { + if (DISABLED_METHODS.has(requestParams.method)) + throw new Error( + `Method ${requestParams.method} is not supported`, + ); const transport = withInjectedTransport ? withInjectedTransport(params) : defaultTransport; @@ -62,10 +75,13 @@ export const useWeb3Transport = ( ({ transportMap, setTransportMap }, chain) => { const [transport, setTransport] = runtimeMutableTransport([ http(backendRpcMap[chain.id], { - batch: true, + batch: { wait: config.PROVIDER_BATCH_TIME }, name: backendRpcMap[chain.id], }), - http(), + http(undefined, { + batch: { wait: config.PROVIDER_BATCH_TIME }, + name: 'default HTTP RPC', + }), ]); return { transportMap: { From d0847d3726e081436fa0b0bacb3053e58229e28f Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 9 Aug 2024 19:36:09 +0700 Subject: [PATCH 06/16] feat: use approve --- .../withdrawals/hooks/contract/useRequest.ts | 2 +- .../wsteth/wrap/hooks/use-wrap-tx-approve.ts | 2 +- shared/hooks/use-allowance.ts | 118 ++++++++++++++++++ shared/hooks/use-balance.ts | 42 +++---- shared/hooks/useApprove.ts | 46 +++---- utils/use-web3-transport.ts | 33 +++-- 6 files changed, 177 insertions(+), 66 deletions(-) create mode 100644 shared/hooks/use-allowance.ts diff --git a/features/withdrawals/hooks/contract/useRequest.ts b/features/withdrawals/hooks/contract/useRequest.ts index 16a4cfba2..e77921856 100644 --- a/features/withdrawals/hooks/contract/useRequest.ts +++ b/features/withdrawals/hooks/contract/useRequest.ts @@ -224,7 +224,7 @@ export const useWithdrawalRequest = ({ approve, needsApprove, allowance, - initialLoading: loadingUseApprove, + isLoading: loadingUseApprove, } = useApprove( valueBN, tokenContract.address, diff --git a/features/wsteth/wrap/hooks/use-wrap-tx-approve.ts b/features/wsteth/wrap/hooks/use-wrap-tx-approve.ts index ada13cc5e..c554be767 100644 --- a/features/wsteth/wrap/hooks/use-wrap-tx-approve.ts +++ b/features/wsteth/wrap/hooks/use-wrap-tx-approve.ts @@ -28,7 +28,7 @@ export const useWrapTxApprove = ({ amount, token }: UseWrapTxApproveArgs) => { approve: processApproveTx, needsApprove, allowance, - loading: isApprovalLoading, + isLoading: isApprovalLoading, } = useApprove( amount, stethTokenAddress, diff --git a/shared/hooks/use-allowance.ts b/shared/hooks/use-allowance.ts new file mode 100644 index 000000000..2111f11ac --- /dev/null +++ b/shared/hooks/use-allowance.ts @@ -0,0 +1,118 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { BigNumber } from 'ethers'; +import { useCallback, useMemo } from 'react'; +import { Address } from 'viem'; +import { useReadContract, useWatchContractEvent } from 'wagmi'; + +const nativeToBN = (data: bigint) => BigNumber.from(data.toString()); + +const Erc20AllowanceAbi = [ + { + type: 'event', + name: 'Approval', + inputs: [ + { indexed: true, name: 'owner', type: 'address' }, + { indexed: true, name: 'spender', type: 'address' }, + { indexed: false, name: 'value', type: 'uint256' }, + ], + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { indexed: true, name: 'from', type: 'address' }, + { indexed: true, name: 'to', type: 'address' }, + { indexed: false, name: 'value', type: 'uint256' }, + ], + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, +] as const; + +type UseAllowanceProps = { + token: Address; + account: Address; + spender: Address; +}; + +export const useAllowance = ({ + token, + account, + spender, +}: UseAllowanceProps) => { + const queryClient = useQueryClient(); + const enabled = !!(token && account && spender); + + const allowanceQuery = useReadContract({ + abi: Erc20AllowanceAbi, + address: token, + functionName: 'allowance', + args: [account, spender], + query: { enabled, select: nativeToBN }, + }); + + const onLogs = useCallback(() => { + void queryClient.invalidateQueries( + { + queryKey: allowanceQuery.queryKey, + }, + { cancelRefetch: false }, + ); + }, [allowanceQuery.queryKey, queryClient]); + + useWatchContractEvent({ + abi: Erc20AllowanceAbi, + eventName: 'Approval', + batch: true, + poll: true, + args: useMemo( + () => ({ + owner: account, + spender, + }), + [account, spender], + ), + address: token, + enabled, + onLogs, + }); + + useWatchContractEvent({ + abi: Erc20AllowanceAbi, + eventName: 'Transfer', + batch: false, + poll: true, + args: useMemo( + () => ({ + from: account, + }), + [account], + ), + address: token, + enabled, + onLogs, + }); + + return allowanceQuery; +}; diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index e1f9c9221..6fbb73603 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -58,15 +58,6 @@ type SubscribeArgs = { }; export const Erc20EventsAbi = [ - { - type: 'event', - name: 'Approval', - inputs: [ - { indexed: true, name: 'owner', type: 'address' }, - { indexed: true, name: 'spender', type: 'address' }, - { indexed: false, name: 'value', type: 'uint256' }, - ], - }, { type: 'event', name: 'Transfer', @@ -114,11 +105,12 @@ export const useTokenTransferSubscription = () => { useWatchContractEvent({ abi: Erc20EventsAbi, eventName: 'Transfer', - batch: true, - poll: true, - args: { - to: address, - }, + args: useMemo( + () => ({ + to: address, + }), + [address], + ), address: tokens, enabled: shouldWatch, onLogs, @@ -127,11 +119,12 @@ export const useTokenTransferSubscription = () => { useWatchContractEvent({ abi: Erc20EventsAbi, eventName: 'Transfer', - batch: true, - poll: true, - args: { - from: address, - }, + args: useMemo( + () => ({ + from: address, + }), + [address], + ), address: tokens, enabled: shouldWatch, onLogs, @@ -177,7 +170,7 @@ export const useTokenTransferSubscription = () => { // NB: contract can be undefined but for better wagmi typings is casted as NoNNullable const useTokenBalance = (contract: TokenContract, address?: Address) => { const { subscribeToTokenUpdates } = useLidoSDK(); - // const queryClient = useQueryClient(); + const balanceQuery = useReadContract({ abi: contract?.abi, address: contract?.address, @@ -193,12 +186,9 @@ const useTokenBalance = (contract: TokenContract, address?: Address) => { queryKey: balanceQuery.queryKey, }); } - }, [ - address, - contract?.address, - balanceQuery.queryKey, - subscribeToTokenUpdates, - ]); + // queryKey causes rerender + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address, contract?.address]); return balanceQuery; }; diff --git a/shared/hooks/useApprove.ts b/shared/hooks/useApprove.ts index 4c962f330..fc5960569 100644 --- a/shared/hooks/useApprove.ts +++ b/shared/hooks/useApprove.ts @@ -4,14 +4,15 @@ import { useCallback } from 'react'; import type { ContractReceipt } from '@ethersproject/contracts'; import { BigNumber } from '@ethersproject/bignumber'; import { getERC20Contract } from '@lido-sdk/contracts'; -import { useAllowance, useSDK } from '@lido-sdk/react'; +import { useSDK } from '@lido-sdk/react'; import { isContract } from 'utils/isContract'; import { runWithTransactionLogger } from 'utils'; import { useCurrentStaticRpcProvider } from './use-current-static-rpc-provider'; -import { STRATEGY_LAZY } from 'consts/swr-strategies'; import { sendTx } from 'utils/send-tx'; +import { useAllowance } from './use-allowance'; +import { Address } from 'viem'; type ApproveOptions = | { @@ -23,12 +24,9 @@ type ApproveOptions = export type UseApproveResponse = { approve: (options?: ApproveOptions) => Promise; + allowance: ReturnType['data']; needsApprove: boolean; - initialLoading: boolean; - allowance: BigNumber | undefined; - loading: boolean; - error: unknown; -}; +} & ReturnType; export const useApprove = ( amount: BigNumber, @@ -43,11 +41,14 @@ export const useApprove = ( invariant(token != null, 'Token is required'); invariant(spender != null, 'Spender is required'); - const result = useAllowance(token, spender, mergedOwner, STRATEGY_LAZY); - const { data: allowance, initialLoading, update: updateAllowance } = result; + const allowanceQuery = useAllowance({ + token: token as Address, + account: mergedOwner as Address, + spender: spender as Address, + }); const needsApprove = Boolean( - !initialLoading && allowance && !amount.isZero() && amount.gt(allowance), + allowanceQuery.data && !amount.isZero() && amount.gt(allowanceQuery.data), ); const approve = useCallback( @@ -86,39 +87,26 @@ export const useApprove = ( await onTxAwaited?.(receipt); } - await updateAllowance(); + await allowanceQuery.refetch(); return approveTxHash; }, [ + providerWeb3, chainId, account, token, - updateAllowance, + staticRpcProvider, + allowanceQuery, spender, amount, - staticRpcProvider, - providerWeb3, ], ); return { approve, needsApprove, - - allowance, - initialLoading, - - /* - * support dependency collection - * https://swr.vercel.app/advanced/performance#dependency-collection - */ - - get loading() { - return result.loading; - }, - get error() { - return result.error; - }, + allowance: allowanceQuery.data, + ...allowanceQuery, }; }; diff --git a/utils/use-web3-transport.ts b/utils/use-web3-transport.ts index 0b887e328..26a594d04 100644 --- a/utils/use-web3-transport.ts +++ b/utils/use-web3-transport.ts @@ -1,17 +1,17 @@ -// TODO: move this to dedicated web3 configuration module - import { config } from 'config'; import { useMemo, useCallback } from 'react'; import { - Transport, + type Transport, fallback, createTransport, http, EIP1193Provider, custom, Chain, + UnsupportedProviderMethodError, } from 'viem'; -import { Connection } from 'wagmi'; +import type { OnResponseFn } from 'viem/_types/clients/transports/fallback'; +import type { Connection } from 'wagmi'; // We disable those methods so wagmi uses getLogs intestead to watch events // Filters are not suitable for public rpc and break between fallbacks @@ -21,6 +21,8 @@ const DISABLED_METHODS = new Set([ 'eth_uninstallFilter', ]); +const NOOP = () => {}; + // Viem transport wrapper that allows runtime changes via setter const runtimeMutableTransport = ( mainTransports: Transport[], @@ -29,26 +31,39 @@ const runtimeMutableTransport = ( return [ (params) => { const defaultTransport = fallback(mainTransports)(params); - + let responseFn: OnResponseFn = NOOP; return createTransport( { key: 'RuntimeMutableTransport', name: 'RuntimeMutableTransport', //@ts-expect-error invalid typings async request(requestParams, options) { - if (DISABLED_METHODS.has(requestParams.method)) - throw new Error( - `Method ${requestParams.method} is not supported`, - ); const transport = withInjectedTransport ? withInjectedTransport(params) : defaultTransport; + + if (DISABLED_METHODS.has(requestParams.method)) { + const error = new UnsupportedProviderMethodError( + new Error(`Method ${requestParams.method} is not supported`), + ); + responseFn({ + error, + method: requestParams.method, + params: params as unknown[], + transport, + status: 'error', + }); + throw error; + } + + transport.value?.onResponse(responseFn); return transport.request(requestParams, options); }, type: 'fallback', }, { transports: defaultTransport.value?.transports, + onResponse: (fn: OnResponseFn) => (responseFn = fn), }, ); }, From fd438611d5bb5f34bf69ae9c5161783428a622d2 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Mon, 12 Aug 2024 14:27:11 +0700 Subject: [PATCH 07/16] fix: ipfs support --- providers/lido-sdk.tsx | 7 ++++++- shared/hooks/use-current-static-rpc-provider.ts | 7 ++++++- shared/hooks/use-mainnet-static-rpc-provider.ts | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/providers/lido-sdk.tsx b/providers/lido-sdk.tsx index ddd18a967..09ebaca09 100644 --- a/providers/lido-sdk.tsx +++ b/providers/lido-sdk.tsx @@ -7,6 +7,7 @@ import { import invariant from 'tiny-invariant'; import { useChainId, useClient, useConnectorClient } from 'wagmi'; import { useTokenTransferSubscription } from 'shared/hooks/use-balance'; +import { useGetRpcUrlByChainId } from 'config/rpc'; type LidoSDKContextValue = { core: LidoSDKCore; @@ -28,6 +29,8 @@ export const LidoSDKProvider = ({ children }: React.PropsWithChildren) => { const subscribe = useTokenTransferSubscription(); const publicClient = useClient(); const chainId = useChainId(); + const getRpcUrl = useGetRpcUrlByChainId(); + const fallbackRpcUrl = !publicClient ? getRpcUrl(chainId) : undefined; const { data: walletClient } = useConnectorClient(); const sdk = useMemo(() => { @@ -36,13 +39,15 @@ export const LidoSDKProvider = ({ children }: React.PropsWithChildren) => { logMode: 'none', rpcProvider: publicClient as any, web3Provider: walletClient as any, + // viem client can be unavailable on ipfs+dev first renders + rpcUrls: !publicClient && fallbackRpcUrl ? [fallbackRpcUrl] : undefined, }); const steth = new LidoSDKstETH({ core }); const wsteth = new LidoSDKwstETH({ core }); return { core, steth, wsteth, subscribeToTokenUpdates: subscribe }; - }, [chainId, publicClient, subscribe, walletClient]); + }, [chainId, fallbackRpcUrl, publicClient, subscribe, walletClient]); return ( {children} ); diff --git a/shared/hooks/use-current-static-rpc-provider.ts b/shared/hooks/use-current-static-rpc-provider.ts index d426728c1..31c82797f 100644 --- a/shared/hooks/use-current-static-rpc-provider.ts +++ b/shared/hooks/use-current-static-rpc-provider.ts @@ -5,6 +5,7 @@ import { getStaticRpcBatchProvider } from '@lido-sdk/providers'; import { StaticJsonRpcBatchProvider } from '@lidofinance/eth-providers'; import { useRpcUrl } from 'config/rpc'; +import { config } from 'config'; export const useCurrentStaticRpcProvider = (): { staticRpcProvider: StaticJsonRpcBatchProvider; @@ -14,7 +15,11 @@ export const useCurrentStaticRpcProvider = (): { const rpcUrl = useRpcUrl(); const staticRpcProvider = useMemo(() => { - return getStaticRpcBatchProvider(chainId, rpcUrl); + return getStaticRpcBatchProvider( + chainId, + rpcUrl, + config.PROVIDER_POLLING_INTERVAL, + ); }, [chainId, rpcUrl]); return { diff --git a/shared/hooks/use-mainnet-static-rpc-provider.ts b/shared/hooks/use-mainnet-static-rpc-provider.ts index 4da4fe368..674a67e59 100644 --- a/shared/hooks/use-mainnet-static-rpc-provider.ts +++ b/shared/hooks/use-mainnet-static-rpc-provider.ts @@ -4,10 +4,15 @@ import { StaticJsonRpcBatchProvider } from '@lidofinance/eth-providers'; import { useGetRpcUrlByChainId } from 'config/rpc'; import { CHAINS } from 'consts/chains'; +import { config } from 'config'; export const useMainnetStaticRpcProvider = (): StaticJsonRpcBatchProvider => { const getRpcUrl = useGetRpcUrlByChainId(); return useMemo(() => { - return getStaticRpcBatchProvider(1, getRpcUrl(CHAINS.Mainnet)); + return getStaticRpcBatchProvider( + CHAINS.Mainnet, + getRpcUrl(CHAINS.Mainnet), + config.PROVIDER_POLLING_INTERVAL, + ); }, [getRpcUrl]); }; From e37dab32af1e7180153a9b5c278f025837eb003e Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 21 Aug 2024 13:41:07 +0700 Subject: [PATCH 08/16] chore: type import --- shared/hooks/useApprove.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/hooks/useApprove.ts b/shared/hooks/useApprove.ts index fc5960569..0e1f2cc14 100644 --- a/shared/hooks/useApprove.ts +++ b/shared/hooks/useApprove.ts @@ -12,7 +12,8 @@ import { runWithTransactionLogger } from 'utils'; import { useCurrentStaticRpcProvider } from './use-current-static-rpc-provider'; import { sendTx } from 'utils/send-tx'; import { useAllowance } from './use-allowance'; -import { Address } from 'viem'; + +import type { Address } from 'viem'; type ApproveOptions = | { From 212546406cb787847aaa5c7ffd98174f1176b4f9 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 21 Aug 2024 15:58:19 +0700 Subject: [PATCH 09/16] fix: remove rpc methods --- pages/api/rpc.ts | 3 -- shared/hooks/use-balance.ts | 74 ++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/pages/api/rpc.ts b/pages/api/rpc.ts index 9e9daa157..01eaa99d1 100644 --- a/pages/api/rpc.ts +++ b/pages/api/rpc.ts @@ -43,9 +43,6 @@ const rpc = rpcFactory({ 'eth_getLogs', 'eth_chainId', 'net_version', - 'eth_newFilter', - 'eth_getFilterChanges', - 'eth_uninstallFilter', ], defaultChain: `${config.defaultChain}`, providers: { diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 6fbb73603..17bdd4681 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -1,4 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ + import { QueryKey, useQuery, useQueryClient } from '@tanstack/react-query'; import { BigNumber } from 'ethers'; import { useLidoSDK } from 'providers/lido-sdk'; @@ -53,7 +55,7 @@ type TokenSubscriptionState = Record< >; type SubscribeArgs = { - address: Address; + tokenAddress: Address; queryKey: QueryKey; }; @@ -85,16 +87,15 @@ export const useTokenTransferSubscription = () => { (logs: Log[]) => { for (const log of logs) { const subscription = subscriptions[log.address]; - if (subscription) { - // we could optimistically update balance data - // but it's easier to refetch balance after transfer - void queryClient.invalidateQueries( - { - queryKey: subscription.queryKey, - }, - { cancelRefetch: false }, - ); - } + if (!subscription) continue; + // we could optimistically update balance data + // but it's easier to refetch balance after transfer + void queryClient.invalidateQueries( + { + queryKey: subscription.queryKey, + }, + { cancelRefetch: false }, + ); } }, [queryClient, subscriptions], @@ -130,36 +131,35 @@ export const useTokenTransferSubscription = () => { onLogs, }); - const subscribe = useCallback(({ address, queryKey }: SubscribeArgs) => { + const subscribe = useCallback(({ tokenAddress, queryKey }: SubscribeArgs) => { setSubscriptions((old) => { - const existing = old[address]; + const existing = old[tokenAddress]; return { ...old, - [address]: { + [tokenAddress]: { queryKey, subscribers: existing?.subscribers ?? 0 + 1, }, }; }); - // unsubscribe + // returns unsubscribe to be used as useEffect return fn (for unmount) return () => { setSubscriptions((old) => { - const existing = old[address]; - if (existing) { - if (existing.subscribers > 1) { - return { - ...old, - [address]: { - ...existing, - subscribers: existing.subscribers - 1, - }, - }; - } else { - delete old[address]; - return { ...old }; - } - } else return old; + const existing = old[tokenAddress]; + if (!existing) return old; + if (existing.subscribers > 1) { + return { + ...old, + [tokenAddress]: { + ...existing, + subscribers: existing.subscribers - 1, + }, + }; + } else { + delete old[tokenAddress]; + return { ...old }; + } }); }; }, []); @@ -182,7 +182,7 @@ const useTokenBalance = (contract: TokenContract, address?: Address) => { useEffect(() => { if (address && contract?.address) { return subscribeToTokenUpdates({ - address: contract.address, + tokenAddress: contract.address, queryKey: balanceQuery.queryKey, }); } @@ -198,16 +198,14 @@ export const useStethBalance = () => { const { steth, core } = useLidoSDK(); - const { data: contractData, isLoading } = useQuery({ + const { data: contract, isLoading } = useQuery({ queryKey: ['steth-contract', core.chainId], enabled: !!address, staleTime: Infinity, queryFn: async () => steth.getContract(), }); - const contract = contractData as NonNullable; - - const balanceData = useTokenBalance(contract, address); + const balanceData = useTokenBalance(contract!, address); return { ...balanceData, isLoading: isLoading || balanceData.isLoading }; }; @@ -217,16 +215,14 @@ export const useWstethBalance = () => { const { wsteth, core } = useLidoSDK(); - const { data: contractData, isLoading } = useQuery({ + const { data: contract, isLoading } = useQuery({ queryKey: ['wsteth-contract', core.chainId], enabled: !!address, staleTime: Infinity, queryFn: async () => wsteth.getContract(), }); - const contract = contractData as NonNullable; - - const balanceData = useTokenBalance(contract, address); + const balanceData = useTokenBalance(contract!, address); return { ...balanceData, isLoading: isLoading || balanceData.isLoading }; }; From 012b9c34d9b4cb5de5dec98bf265cb5356d09ae2 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 22 Aug 2024 15:57:38 +0700 Subject: [PATCH 10/16] fix: rewards balance for inputed address --- .../rewardsListContent/RewardsListContent.tsx | 8 +++- shared/hooks/use-balance.ts | 41 +++++++++++++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/features/rewards/components/rewardsListContent/RewardsListContent.tsx b/features/rewards/components/rewardsListContent/RewardsListContent.tsx index bf496b83d..dac289fb5 100644 --- a/features/rewards/components/rewardsListContent/RewardsListContent.tsx +++ b/features/rewards/components/rewardsListContent/RewardsListContent.tsx @@ -15,9 +15,12 @@ import { ErrorWrapper, } from './RewardsListContentStyles'; +import type { Address } from 'viem'; + export const RewardsListContent: FC = () => { const { error, + address, initialLoading, data, currencyObject, @@ -26,7 +29,10 @@ export const RewardsListContent: FC = () => { isLagging, } = useRewardsHistory(); const { data: stethBalance, isLoading: isStethBalanceLoading } = - useStethBalance(); + useStethBalance({ + account: address as Address, + shouldSubscribeToUpdates: false, + }); const hasSteth = stethBalance?.gt(Zero); if (!data && !initialLoading && !error) return ; diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 17bdd4681..9c0b7f5c0 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -59,6 +59,11 @@ type SubscribeArgs = { queryKey: QueryKey; }; +type UseBalanceProps = { + account?: Address; + shouldSubscribeToUpdates?: boolean; +}; + export const Erc20EventsAbi = [ { type: 'event', @@ -168,7 +173,11 @@ export const useTokenTransferSubscription = () => { }; // NB: contract can be undefined but for better wagmi typings is casted as NoNNullable -const useTokenBalance = (contract: TokenContract, address?: Address) => { +const useTokenBalance = ( + contract: TokenContract, + address?: Address, + shouldSubscribe = true, +) => { const { subscribeToTokenUpdates } = useLidoSDK(); const balanceQuery = useReadContract({ @@ -180,7 +189,7 @@ const useTokenBalance = (contract: TokenContract, address?: Address) => { }); useEffect(() => { - if (address && contract?.address) { + if (shouldSubscribe && address && contract?.address) { return subscribeToTokenUpdates({ tokenAddress: contract.address, queryKey: balanceQuery.queryKey, @@ -193,36 +202,52 @@ const useTokenBalance = (contract: TokenContract, address?: Address) => { return balanceQuery; }; -export const useStethBalance = () => { +export const useStethBalance = ({ + account, + shouldSubscribeToUpdates = true, +}: UseBalanceProps = {}) => { const { address } = useAccount(); + const mergedAccount = account ?? address; const { steth, core } = useLidoSDK(); const { data: contract, isLoading } = useQuery({ queryKey: ['steth-contract', core.chainId], - enabled: !!address, + enabled: !!mergedAccount, staleTime: Infinity, queryFn: async () => steth.getContract(), }); - const balanceData = useTokenBalance(contract!, address); + const balanceData = useTokenBalance( + contract!, + mergedAccount, + shouldSubscribeToUpdates, + ); return { ...balanceData, isLoading: isLoading || balanceData.isLoading }; }; -export const useWstethBalance = () => { +export const useWstethBalance = ({ + account, + shouldSubscribeToUpdates = true, +}: UseBalanceProps = {}) => { const { address } = useAccount(); + const mergedAccount = account ?? address; const { wsteth, core } = useLidoSDK(); const { data: contract, isLoading } = useQuery({ queryKey: ['wsteth-contract', core.chainId], - enabled: !!address, + enabled: !!mergedAccount, staleTime: Infinity, queryFn: async () => wsteth.getContract(), }); - const balanceData = useTokenBalance(contract!, address); + const balanceData = useTokenBalance( + contract!, + mergedAccount, + shouldSubscribeToUpdates, + ); return { ...balanceData, isLoading: isLoading || balanceData.isLoading }; }; From 027451c8fd46c080504edc9b690309388b70b978 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 30 Aug 2024 13:52:28 +0700 Subject: [PATCH 11/16] fix: event subscription --- shared/hooks/use-balance.ts | 77 +++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 9c0b7f5c0..56ebdade8 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -15,7 +15,7 @@ import { import type { AbstractLidoSDKErc20 } from '@lidofinance/lido-ethereum-sdk/erc20'; import type { GetBalanceData } from 'wagmi/query'; -import type { Address, Log } from 'viem'; +import type { Address, WatchContractEventOnLogsFn } from 'viem'; const nativeToBN = (data: bigint) => BigNumber.from(data.toString()); @@ -76,6 +76,12 @@ export const Erc20EventsAbi = [ }, ] as const; +type OnLogsFn = WatchContractEventOnLogsFn< + typeof Erc20EventsAbi, + 'Transfer', + true +>; + export const useTokenTransferSubscription = () => { const { address } = useAccount(); const queryClient = useQueryClient(); @@ -88,10 +94,11 @@ export const useTokenTransferSubscription = () => { [subscriptions], ); - const onLogs = useCallback( - (logs: Log[]) => { + const onLogs: OnLogsFn = useCallback( + (logs) => { for (const log of logs) { - const subscription = subscriptions[log.address]; + const subscription = + subscriptions[log.address.toLowerCase() as Address]; if (!subscription) continue; // we could optimistically update balance data // but it's easier to refetch balance after transfer @@ -136,38 +143,42 @@ export const useTokenTransferSubscription = () => { onLogs, }); - const subscribe = useCallback(({ tokenAddress, queryKey }: SubscribeArgs) => { - setSubscriptions((old) => { - const existing = old[tokenAddress]; - return { - ...old, - [tokenAddress]: { - queryKey, - subscribers: existing?.subscribers ?? 0 + 1, - }, - }; - }); - - // returns unsubscribe to be used as useEffect return fn (for unmount) - return () => { + const subscribe = useCallback( + ({ tokenAddress: _tokenAddress, queryKey }: SubscribeArgs) => { + const tokenAddress = _tokenAddress.toLowerCase() as Address; setSubscriptions((old) => { const existing = old[tokenAddress]; - if (!existing) return old; - if (existing.subscribers > 1) { - return { - ...old, - [tokenAddress]: { - ...existing, - subscribers: existing.subscribers - 1, - }, - }; - } else { - delete old[tokenAddress]; - return { ...old }; - } + return { + ...old, + [tokenAddress]: { + queryKey, + subscribers: existing?.subscribers ?? 0 + 1, + }, + }; }); - }; - }, []); + + // returns unsubscribe to be used as useEffect return fn (for unmount) + return () => { + setSubscriptions((old) => { + const existing = old[tokenAddress]; + if (!existing) return old; + if (existing.subscribers > 1) { + return { + ...old, + [tokenAddress]: { + ...existing, + subscribers: existing.subscribers - 1, + }, + }; + } else { + delete old[tokenAddress]; + return { ...old }; + } + }); + }; + }, + [], + ); return subscribe; }; From c6a0730b730426b3756648d2cdbad063f6dcaeec Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 30 Aug 2024 15:30:29 +0700 Subject: [PATCH 12/16] fix: await tx with wagmi --- config/groups/web3.ts | 2 +- features/stake/stake-form/use-stake.ts | 16 +++-- .../withdrawals/hooks/contract/useClaim.ts | 9 ++- .../withdrawals/hooks/contract/useRequest.ts | 26 ++++----- .../hooks/use-unwrap-form-processing.ts | 58 ++++++++++++++----- .../unwrap/hooks/use-unwrap-tx-processing.ts | 38 ------------ .../wrap/hooks/use-wrap-form-processing.ts | 31 +++++++--- shared/hooks/use-tx-conformation.ts | 19 ++++++ shared/hooks/useApprove.ts | 14 ++--- 9 files changed, 116 insertions(+), 97 deletions(-) delete mode 100644 features/wsteth/unwrap/hooks/use-unwrap-tx-processing.ts create mode 100644 shared/hooks/use-tx-conformation.ts diff --git a/config/groups/web3.ts b/config/groups/web3.ts index 3e072920d..0cbb31961 100644 --- a/config/groups/web3.ts +++ b/config/groups/web3.ts @@ -1,7 +1,7 @@ import { parseEther } from '@ethersproject/units'; // interval in ms for RPC event polling for token balance and tx updates -export const PROVIDER_POLLING_INTERVAL = 12_000; +export const PROVIDER_POLLING_INTERVAL = 7_000; // how long in ms to wait for RPC batching(multicall and provider) export const PROVIDER_BATCH_TIME = 150; diff --git a/features/stake/stake-form/use-stake.ts b/features/stake/stake-form/use-stake.ts index a772a63f5..2d9646661 100644 --- a/features/stake/stake-form/use-stake.ts +++ b/features/stake/stake-form/use-stake.ts @@ -18,6 +18,7 @@ import { MockLimitReachedError, getAddress } from './utils'; import { useTxModalStagesStake } from './hooks/use-tx-modal-stages-stake'; import { sendTx } from 'utils/send-tx'; +import { useTxConfirmation } from 'shared/hooks/use-tx-conformation'; type StakeArguments = { amount: BigNumber | null; @@ -32,16 +33,16 @@ type StakeOptions = { export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { const stethContractWeb3 = useSTETHContractWeb3(); const stethContract = useSTETHContractRPC(); - const { account, chainId } = useWeb3(); + const { account } = useWeb3(); const { staticRpcProvider } = useCurrentStaticRpcProvider(); const { providerWeb3 } = useSDK(); const { txModalStages } = useTxModalStagesStake(); + const waitForTx = useTxConfirmation(); return useCallback( async ({ amount, referral }: StakeArguments): Promise => { try { invariant(amount, 'amount is null'); - invariant(chainId, 'chainId is not defined'); invariant(account, 'account is not defined'); invariant(providerWeb3, 'providerWeb3 not defined'); invariant(stethContractWeb3, 'steth is not defined'); @@ -92,14 +93,11 @@ export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { txModalStages.pending(amount, txHash); - if (!isMultisig) { - await runWithTransactionLogger('Stake block confirmation', () => - staticRpcProvider.waitForTransaction(txHash), - ); - } + await runWithTransactionLogger('Stake block confirmation', () => + waitForTx(txHash), + ); const stethBalance = await stethContract.balanceOf(account); - await onConfirm?.(); txModalStages.success(stethBalance, txHash); @@ -112,7 +110,6 @@ export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { } }, [ - chainId, account, providerWeb3, stethContractWeb3, @@ -120,6 +117,7 @@ export const useStake = ({ onConfirm, onRetry }: StakeOptions) => { staticRpcProvider, stethContract, onConfirm, + waitForTx, onRetry, ], ); diff --git a/features/withdrawals/hooks/contract/useClaim.ts b/features/withdrawals/hooks/contract/useClaim.ts index 0e55e9bcf..6051f0c76 100644 --- a/features/withdrawals/hooks/contract/useClaim.ts +++ b/features/withdrawals/hooks/contract/useClaim.ts @@ -13,6 +13,7 @@ import { useSDK } from '@lido-sdk/react'; import { useTxModalStagesClaim } from 'features/withdrawals/claim/transaction-modal-claim/use-tx-modal-stages-claim'; import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider'; import { sendTx } from 'utils/send-tx'; +import { useTxConfirmation } from 'shared/hooks/use-tx-conformation'; type Args = { onRetry?: () => void; @@ -25,6 +26,7 @@ export const useClaim = ({ onRetry }: Args) => { const { staticRpcProvider } = useCurrentStaticRpcProvider(); const { optimisticClaimRequests } = useClaimData(); const { txModalStages } = useTxModalStagesClaim(); + const waitForTx = useTxConfirmation(); return useCallback( async (sortedRequests: RequestStatusClaimable[]) => { @@ -70,8 +72,8 @@ export const useClaim = ({ onRetry }: Args) => { txModalStages.pending(amount, txHash); - await runWithTransactionLogger('Claim block confirmation', async () => - staticRpcProvider.waitForTransaction(txHash), + await runWithTransactionLogger('Claim block confirmation', () => + waitForTx(txHash), ); await optimisticClaimRequests(sortedRequests); @@ -89,8 +91,9 @@ export const useClaim = ({ onRetry }: Args) => { account, providerWeb3, txModalStages, - staticRpcProvider, optimisticClaimRequests, + staticRpcProvider, + waitForTx, onRetry, ], ); diff --git a/features/withdrawals/hooks/contract/useRequest.ts b/features/withdrawals/hooks/contract/useRequest.ts index e77921856..3f234e400 100644 --- a/features/withdrawals/hooks/contract/useRequest.ts +++ b/features/withdrawals/hooks/contract/useRequest.ts @@ -29,12 +29,13 @@ import { useTxModalStagesRequest } from 'features/withdrawals/request/transactio import { useTransactionModal } from 'shared/transaction-modal/transaction-modal'; import { sendTx } from 'utils/send-tx'; import { overrideWithQAMockBoolean } from 'utils/qa'; +import { useTxConfirmation } from 'shared/hooks/use-tx-conformation'; // this encapsulates permit/approval & steth/wsteth flows const useWithdrawalRequestMethods = () => { const { providerWeb3 } = useSDK(); const { staticRpcProvider } = useCurrentStaticRpcProvider(); - const { account, chainId, contractWeb3 } = useWithdrawalsContract(); + const { account, contractWeb3 } = useWithdrawalsContract(); const permitSteth = useCallback( async ({ @@ -44,8 +45,6 @@ const useWithdrawalRequestMethods = () => { signature?: GatherPermitSignatureResult; requests: BigNumber[]; }) => { - invariant(chainId, 'must have chainId'); - invariant(account, 'must have account'); invariant(providerWeb3, 'must have providerWeb3'); invariant(signature, 'must have signature'); invariant(contractWeb3, 'must have contractWeb3'); @@ -73,7 +72,7 @@ const useWithdrawalRequestMethods = () => { return callback; }, - [account, chainId, contractWeb3, providerWeb3, staticRpcProvider], + [contractWeb3, providerWeb3, staticRpcProvider], ); const permitWsteth = useCallback( @@ -84,8 +83,6 @@ const useWithdrawalRequestMethods = () => { signature?: GatherPermitSignatureResult; requests: BigNumber[]; }) => { - invariant(chainId, 'must have chainId'); - invariant(account, 'must have account'); invariant(signature, 'must have signature'); invariant(providerWeb3, 'must have providerWeb3'); invariant(contractWeb3, 'must have contractWeb3'); @@ -113,12 +110,11 @@ const useWithdrawalRequestMethods = () => { return callback; }, - [account, chainId, contractWeb3, providerWeb3, staticRpcProvider], + [contractWeb3, providerWeb3, staticRpcProvider], ); const steth = useCallback( async ({ requests }: { requests: BigNumber[] }) => { - invariant(chainId, 'must have chainId'); invariant(account, 'must have account'); invariant(contractWeb3, 'must have contractWeb3'); invariant(providerWeb3, 'must have providerWeb3'); @@ -140,12 +136,11 @@ const useWithdrawalRequestMethods = () => { return callback; }, - [account, chainId, contractWeb3, staticRpcProvider, providerWeb3], + [account, contractWeb3, staticRpcProvider, providerWeb3], ); const wstETH = useCallback( async ({ requests }: { requests: BigNumber[] }) => { - invariant(chainId, 'must have chainId'); invariant(account, 'must have account'); invariant(contractWeb3, 'must have contractWeb3'); invariant(providerWeb3, 'must have providerWeb3'); @@ -167,7 +162,7 @@ const useWithdrawalRequestMethods = () => { return callback; }, - [account, chainId, contractWeb3, staticRpcProvider, providerWeb3], + [account, contractWeb3, staticRpcProvider, providerWeb3], ); return useCallback( @@ -202,7 +197,6 @@ export const useWithdrawalRequest = ({ }: useWithdrawalRequestParams) => { const { chainId } = useSDK(); const withdrawalQueueAddress = getWithdrawalQueueAddress(chainId); - const { staticRpcProvider } = useCurrentStaticRpcProvider(); const { connector } = useAccount(); const { account } = useWeb3(); @@ -210,6 +204,7 @@ export const useWithdrawalRequest = ({ const { txModalStages } = useTxModalStagesRequest(); const getRequestMethod = useWithdrawalRequestMethods(); const { isMultisig, isLoading: isMultisigLoading } = useIsMultisig(); + const waitForTx = useTxConfirmation(); const wstethContract = useWSTETHContractRPC(); const stethContract = useSTETHContractRPC(); @@ -322,8 +317,9 @@ export const useWithdrawalRequest = ({ txModalStages.pending(amount, token, txHash); if (!isMultisig) { - await runWithTransactionLogger('Stake block confirmation', () => - staticRpcProvider.waitForTransaction(txHash), + await runWithTransactionLogger( + 'Withdrawal Request block confirmation', + () => waitForTx(txHash), ); } @@ -347,8 +343,8 @@ export const useWithdrawalRequest = ({ needsApprove, onConfirm, onRetry, - staticRpcProvider, txModalStages, + waitForTx, ], ); diff --git a/features/wsteth/unwrap/hooks/use-unwrap-form-processing.ts b/features/wsteth/unwrap/hooks/use-unwrap-form-processing.ts index 21450e3f5..f7fec3819 100644 --- a/features/wsteth/unwrap/hooks/use-unwrap-form-processing.ts +++ b/features/wsteth/unwrap/hooks/use-unwrap-form-processing.ts @@ -1,18 +1,24 @@ import invariant from 'tiny-invariant'; import { useCallback } from 'react'; -import { useSTETHContractRPC, useWSTETHContractRPC } from '@lido-sdk/react'; +import { + useSDK, + useSTETHContractRPC, + useWSTETHContractRPC, + useWSTETHContractWeb3, +} from '@lido-sdk/react'; import { useWeb3 } from 'reef-knot/web3-react'; -import { useUnwrapTxProcessing } from './use-unwrap-tx-processing'; import { useTxModalStagesUnwrap } from './use-tx-modal-stages-unwrap'; import { isContract } from 'utils/isContract'; import { runWithTransactionLogger } from 'utils'; import type { UnwrapFormInputType } from '../unwrap-form-context'; import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider'; +import { sendTx } from 'utils/send-tx'; +import { useTxConfirmation } from 'shared/hooks/use-tx-conformation'; type UseUnwrapFormProcessorArgs = { - onConfirm?: () => Promise; + onConfirm: () => Promise; onRetry?: () => void; }; @@ -21,24 +27,42 @@ export const useUnwrapFormProcessor = ({ onRetry, }: UseUnwrapFormProcessorArgs) => { const { account } = useWeb3(); + const { providerWeb3 } = useSDK(); const { staticRpcProvider } = useCurrentStaticRpcProvider(); - const processWrapTx = useUnwrapTxProcessing(); + const { txModalStages } = useTxModalStagesUnwrap(); const stETHContractRPC = useSTETHContractRPC(); const wstETHContractRPC = useWSTETHContractRPC(); - const { txModalStages } = useTxModalStagesUnwrap(); + const wstethContractWeb3 = useWSTETHContractWeb3(); + const waitForTx = useTxConfirmation(); return useCallback( async ({ amount }: UnwrapFormInputType) => { try { invariant(amount, 'amount should be presented'); invariant(account, 'address should be presented'); - const isMultisig = await isContract(account, staticRpcProvider); - const willReceive = await wstETHContractRPC.getStETHByWstETH(amount); + invariant(providerWeb3, 'providerWeb3 must be presented'); + invariant(wstethContractWeb3, 'must have wstethContractWeb3'); + + const [isMultisig, willReceive] = await Promise.all([ + isContract(account, staticRpcProvider), + wstETHContractRPC.getStETHByWstETH(amount), + ]); txModalStages.sign(amount, willReceive); - const txHash = await runWithTransactionLogger('Unwrap signing', () => - processWrapTx({ amount, isMultisig }), + const txHash = await runWithTransactionLogger( + 'Unwrap signing', + async () => { + const tx = + await wstethContractWeb3.populateTransaction.unwrap(amount); + + return sendTx({ + tx, + isMultisig, + staticProvider: staticRpcProvider, + walletProvider: providerWeb3, + }); + }, ); if (isMultisig) { @@ -48,13 +72,15 @@ export const useUnwrapFormProcessor = ({ txModalStages.pending(amount, willReceive, txHash); - await runWithTransactionLogger('Unwrap block confirmation', async () => - staticRpcProvider.waitForTransaction(txHash), + await runWithTransactionLogger('Unwrap block confirmation', () => + waitForTx(txHash), ); - const stethBalance = await stETHContractRPC.balanceOf(account); + const [stethBalance] = await Promise.all([ + stETHContractRPC.balanceOf(account), + onConfirm(), + ]); - await onConfirm?.(); txModalStages.success(stethBalance, txHash); return true; } catch (error: any) { @@ -65,12 +91,14 @@ export const useUnwrapFormProcessor = ({ }, [ account, + providerWeb3, + wstethContractWeb3, + staticRpcProvider, wstETHContractRPC, txModalStages, stETHContractRPC, onConfirm, - processWrapTx, - staticRpcProvider, + waitForTx, onRetry, ], ); diff --git a/features/wsteth/unwrap/hooks/use-unwrap-tx-processing.ts b/features/wsteth/unwrap/hooks/use-unwrap-tx-processing.ts deleted file mode 100644 index eb0742eed..000000000 --- a/features/wsteth/unwrap/hooks/use-unwrap-tx-processing.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useCallback } from 'react'; -import invariant from 'tiny-invariant'; - -import { useSDK, useWSTETHContractWeb3 } from '@lido-sdk/react'; - -import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider'; - -import type { UnwrapFormInputType } from '../unwrap-form-context'; -import { sendTx } from 'utils/send-tx'; - -type UnwrapTxProcessorArgs = Omit & { - isMultisig: boolean; -}; - -export const useUnwrapTxProcessing = () => { - const { chainId, providerWeb3 } = useSDK(); - const { staticRpcProvider } = useCurrentStaticRpcProvider(); - const wstethContractWeb3 = useWSTETHContractWeb3(); - - return useCallback( - async ({ isMultisig, amount }: UnwrapTxProcessorArgs) => { - invariant(amount, 'amount id must be presented'); - invariant(chainId, 'chain id must be presented'); - invariant(providerWeb3, 'providerWeb3 must be presented'); - invariant(wstethContractWeb3, 'must have wstethContractWeb3'); - - const tx = await wstethContractWeb3.populateTransaction.unwrap(amount); - - return sendTx({ - tx, - isMultisig, - staticProvider: staticRpcProvider, - walletProvider: providerWeb3, - }); - }, - [chainId, providerWeb3, staticRpcProvider, wstethContractWeb3], - ); -}; diff --git a/features/wsteth/wrap/hooks/use-wrap-form-processing.ts b/features/wsteth/wrap/hooks/use-wrap-form-processing.ts index 3c5daa786..cbccc544e 100644 --- a/features/wsteth/wrap/hooks/use-wrap-form-processing.ts +++ b/features/wsteth/wrap/hooks/use-wrap-form-processing.ts @@ -3,20 +3,23 @@ import invariant from 'tiny-invariant'; import { useCallback } from 'react'; import { useWeb3 } from 'reef-knot/web3-react'; import { useWrapTxProcessing } from './use-wrap-tx-processing'; + import { useTxModalWrap } from './use-tx-modal-stages-wrap'; -import { useWSTETHContractRPC } from '@lido-sdk/react'; +import { useSDK, useWSTETHContractRPC } from '@lido-sdk/react'; import { runWithTransactionLogger } from 'utils'; import { isContract } from 'utils/isContract'; +import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider'; +import { useTxConfirmation } from 'shared/hooks/use-tx-conformation'; + import type { WrapFormApprovalData, WrapFormInputType, } from '../wrap-form-context'; -import { useCurrentStaticRpcProvider } from 'shared/hooks/use-current-static-rpc-provider'; type UseWrapFormProcessorArgs = { approvalData: WrapFormApprovalData; - onConfirm?: () => Promise; + onConfirm: () => Promise; onRetry?: () => void; }; @@ -30,15 +33,21 @@ export const useWrapFormProcessor = ({ const { isApprovalNeededBeforeWrap, processApproveTx } = approvalData; const { txModalStages } = useTxModalWrap(); const wstETHContractRPC = useWSTETHContractRPC(); + const waitForTx = useTxConfirmation(); const { staticRpcProvider } = useCurrentStaticRpcProvider(); + const { providerWeb3 } = useSDK(); return useCallback( async ({ amount, token }: WrapFormInputType) => { try { invariant(amount, 'amount should be presented'); invariant(account, 'address should be presented'); - const isMultisig = await isContract(account, staticRpcProvider); - const willReceive = await wstETHContractRPC.getWstETHByStETH(amount); + invariant(providerWeb3, 'providerWeb3 should be presented'); + + const [isMultisig, willReceive] = await Promise.all([ + isContract(account, staticRpcProvider), + wstETHContractRPC.getWstETHByStETH(amount), + ]); if (isApprovalNeededBeforeWrap) { txModalStages.signApproval(amount, token); @@ -70,12 +79,14 @@ export const useWrapFormProcessor = ({ txModalStages.pending(amount, token, willReceive, txHash); await runWithTransactionLogger('Wrap block confirmation', () => - staticRpcProvider.waitForTransaction(txHash), + waitForTx(txHash), ); - const wstethBalance = await wstETHContractRPC.balanceOf(account); + const [wstethBalance] = await Promise.all([ + wstETHContractRPC.balanceOf(account), + onConfirm(), + ]); - await onConfirm?.(); txModalStages.success(wstethBalance, txHash); return true; } catch (error) { @@ -86,13 +97,15 @@ export const useWrapFormProcessor = ({ }, [ account, + providerWeb3, + staticRpcProvider, wstETHContractRPC, isApprovalNeededBeforeWrap, txModalStages, onConfirm, processApproveTx, processWrapTx, - staticRpcProvider, + waitForTx, onRetry, ], ); diff --git a/shared/hooks/use-tx-conformation.ts b/shared/hooks/use-tx-conformation.ts new file mode 100644 index 000000000..7560046ef --- /dev/null +++ b/shared/hooks/use-tx-conformation.ts @@ -0,0 +1,19 @@ +import { useCallback } from 'react'; +import type { Hash } from 'viem'; +import { waitForTransactionReceipt } from 'viem/actions'; +import { useClient } from 'wagmi'; + +// helper hook until migration to wagmi is complete +// awaits TX trough wagmi transport to allow sync with balance hooks +export const useTxConfirmation = () => { + const client = useClient(); + return useCallback( + (hash: string) => { + return waitForTransactionReceipt(client as any, { + confirmations: 1, + hash: hash as Hash, + }); + }, + [client], + ); +}; diff --git a/shared/hooks/useApprove.ts b/shared/hooks/useApprove.ts index 0e1f2cc14..3d26c6043 100644 --- a/shared/hooks/useApprove.ts +++ b/shared/hooks/useApprove.ts @@ -1,7 +1,6 @@ import invariant from 'tiny-invariant'; import { useCallback } from 'react'; -import type { ContractReceipt } from '@ethersproject/contracts'; import { BigNumber } from '@ethersproject/bignumber'; import { getERC20Contract } from '@lido-sdk/contracts'; import { useSDK } from '@lido-sdk/react'; @@ -12,14 +11,15 @@ import { runWithTransactionLogger } from 'utils'; import { useCurrentStaticRpcProvider } from './use-current-static-rpc-provider'; import { sendTx } from 'utils/send-tx'; import { useAllowance } from './use-allowance'; +import { useTxConfirmation } from './use-tx-conformation'; -import type { Address } from 'viem'; +import type { Address, TransactionReceipt } from 'viem'; type ApproveOptions = | { onTxStart?: () => void | Promise; onTxSent?: (tx: string) => void | Promise; - onTxAwaited?: (tx: ContractReceipt) => void | Promise; + onTxAwaited?: (tx: TransactionReceipt) => void | Promise; } | undefined; @@ -35,8 +35,9 @@ export const useApprove = ( spender: string, owner?: string, ): UseApproveResponse => { - const { providerWeb3, account, chainId } = useSDK(); + const { providerWeb3, account } = useSDK(); const { staticRpcProvider } = useCurrentStaticRpcProvider(); + const waitForTx = useTxConfirmation(); const mergedOwner = owner ?? account; invariant(token != null, 'Token is required'); @@ -55,7 +56,6 @@ export const useApprove = ( const approve = useCallback( async ({ onTxStart, onTxSent, onTxAwaited } = {}) => { invariant(providerWeb3 != null, 'Web3 provider is required'); - invariant(chainId, 'chain id is required'); invariant(account, 'account is required'); await onTxStart?.(); const contractWeb3 = getERC20Contract(token, providerWeb3.getSigner()); @@ -83,7 +83,7 @@ export const useApprove = ( if (!isMultisig) { const receipt = await runWithTransactionLogger( 'Approve block confirmation', - () => staticRpcProvider.waitForTransaction(approveTxHash), + () => waitForTx(approveTxHash), ); await onTxAwaited?.(receipt); } @@ -94,13 +94,13 @@ export const useApprove = ( }, [ providerWeb3, - chainId, account, token, staticRpcProvider, allowanceQuery, spender, amount, + waitForTx, ], ); From 7be1cbaa4ff2679bec35f44fe61a927f0e18a5a7 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 5 Sep 2024 19:11:28 +0700 Subject: [PATCH 13/16] fix: ipfs rpc check on infra --- providers/ipfs-info-box-statuses.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/providers/ipfs-info-box-statuses.tsx b/providers/ipfs-info-box-statuses.tsx index 76823e0d0..c79cc6959 100644 --- a/providers/ipfs-info-box-statuses.tsx +++ b/providers/ipfs-info-box-statuses.tsx @@ -9,6 +9,7 @@ import { import { useLidoSWR, useLocalStorage, useSDK } from '@lido-sdk/react'; import invariant from 'tiny-invariant'; +import { config } from 'config'; import { useRpcUrl } from 'config/rpc'; import { SETTINGS_PATH } from 'consts/urls'; import { STRATEGY_LAZY } from 'consts/swr-strategies'; @@ -56,7 +57,7 @@ export const IPFSInfoBoxStatusesProvider: FC = ({ const { data: isRPCAvailableRaw, initialLoading: isLoading } = useLidoSWR( `rpc-url-check-${rpcUrl}-${chainId}`, async () => await checkRpcUrl(rpcUrl, chainId), - STRATEGY_LAZY, + { ...STRATEGY_LAZY, isPaused: () => !config.ipfsMode }, ); const isRPCAvailable = isRPCAvailableRaw === true; From fef840ab8bdd12d335d59c5a205f9d60c0c077ec Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 5 Sep 2024 19:26:27 +0700 Subject: [PATCH 14/16] chore: up deps --- package.json | 20 ++++++------- yarn.lock | 82 ++++++++++++++++++++++++++-------------------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 3bb507cc8..b75e90ed5 100644 --- a/package.json +++ b/package.json @@ -33,17 +33,17 @@ "@lido-sdk/helpers": "^1.6.0", "@lido-sdk/providers": "^1.4.15", "@lido-sdk/react": "^2.0.6", - "@lidofinance/analytics-matomo": "^0.45.0", - "@lidofinance/api-metrics": "^0.45.0", - "@lidofinance/api-rpc": "^0.45.0", - "@lidofinance/eth-api-providers": "^0.45.0", - "@lidofinance/eth-providers": "^0.45.0", + "@lidofinance/analytics-matomo": "^0.45.1", + "@lidofinance/api-metrics": "^0.45.1", + "@lidofinance/api-rpc": "^0.45.1", + "@lidofinance/eth-api-providers": "^0.45.1", + "@lidofinance/eth-providers": "^0.45.1", "@lidofinance/lido-ui": "^3.26.0", - "@lidofinance/next-api-wrapper": "^0.45.0", - "@lidofinance/next-ip-rate-limit": "^0.45.0", - "@lidofinance/next-pages": "^0.45.0", - "@lidofinance/rpc": "^0.45.0", - "@lidofinance/satanizer": "^0.45.0", + "@lidofinance/next-api-wrapper": "^0.45.1", + "@lidofinance/next-ip-rate-limit": "^0.45.1", + "@lidofinance/next-pages": "^0.45.1", + "@lidofinance/rpc": "^0.45.1", + "@lidofinance/satanizer": "^0.45.1", "@tanstack/react-query": "^5.51.21", "bignumber.js": "9.1.0", "copy-to-clipboard": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index e1c483533..422beab7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2311,20 +2311,20 @@ tiny-invariant "^1.1.0" tiny-warning "^1.0.3" -"@lidofinance/analytics-matomo@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/analytics-matomo/-/analytics-matomo-0.45.0.tgz#288c82e193679c2c2654c4e434ccab81f99b7335" - integrity sha512-f+NAMPUrHlnCDzLwBPgoNNf8xmwW3AMm7J3Vb/Y0as96H7gMFoqvpfyzRwOF3ov9/ZIieLKoFThTn/VFWHVwYg== +"@lidofinance/analytics-matomo@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/analytics-matomo/-/analytics-matomo-0.45.1.tgz#f1ee0df1f6babb1f806e258831efd4ba568ad188" + integrity sha512-D35q+0XnqhWkKJidmT0iWKtLZ+KDj8z9J3t3wP+D0zi53+bP9dd7sU1ciZBUal1Sp0IjEzfiGVSiWKFQqyB8Pg== -"@lidofinance/api-metrics@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/api-metrics/-/api-metrics-0.45.0.tgz#5991b49544a9477c3f14e59dc6d84daed46c96d1" - integrity sha512-CMoV2RFzexw4lsNTW0RTMo6aaJ58iq7uWtmGuF0JxyB4HFfr+8uDr1v6IdJSK+qjfQeATfOC8EnSJ2C5QKUcpg== +"@lidofinance/api-metrics@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/api-metrics/-/api-metrics-0.45.1.tgz#0c093ebeb5fa3b2b73533ec933e0bbd20bbb37dd" + integrity sha512-iPzE4RzPxeApCF7+rIQcng/zos7gHY4ibTBkTF4Vt32P9aCtt70YinQ2Sf2KY8AnBeWhGJLXjvAPWr9P3OI18A== -"@lidofinance/api-rpc@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/api-rpc/-/api-rpc-0.45.0.tgz#0de2534c225c3a4271bb9cf55a228a17273aa222" - integrity sha512-t3UIuXt65nKQNP+e6thr6pAZ+8iKc7H/9vyD7PAhTuxNRMNj+PiVSZAErAjJjbo9/jbEHlgApsgpYwx4qAYIIA== +"@lidofinance/api-rpc@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/api-rpc/-/api-rpc-0.45.1.tgz#0a2002e233bd1233ed68d3a479a41ed93868a6d8" + integrity sha512-rTuLPs0EiOZa3tap2+zAgJ1/kpygjX+R5LgfkBw5oxZn1Dj9k+XvWuPAWBkHAsNVKonYTucD4F/NP0XhNlYuYQ== "@lidofinance/eslint-config@^0.34.0": version "0.34.0" @@ -2333,15 +2333,15 @@ dependencies: typescript "^4.7" -"@lidofinance/eth-api-providers@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/eth-api-providers/-/eth-api-providers-0.45.0.tgz#99160808cba97fca6823fdc9d15d4299db509808" - integrity sha512-B6F/Y0i8L6EiZt1AJk1b4FYMm/CqlBJQzFjWpwegP03wNOh/l84BxQcM/i9PPenV7mm3+RNC/qb4h7v6/L5lEA== +"@lidofinance/eth-api-providers@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/eth-api-providers/-/eth-api-providers-0.45.1.tgz#090e6a3957b664ce54641453357850cac0022a97" + integrity sha512-/8BbPUmBUhB8hRHZpzFXcK/i2jk7pmzhh2NJWjya+G3nSmXaw8U6PInpArB/ZxRAErs0zufUIOm4w7Qrr9mVPw== -"@lidofinance/eth-providers@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.45.0.tgz#8c49b4f3ae1316f3901fd8988ae8cd5918dbdfed" - integrity sha512-NAn+Eu0HL5cveC2gpl3fJLXRboKDJ0fO3ZTmgn94RuJEG4HoakXktaJUNnzFmLjUuHrhxUkqmIKtb9cT+C2bNg== +"@lidofinance/eth-providers@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.45.1.tgz#54cbd893c92c06f7ad605ebe0fd8059fc326f180" + integrity sha512-ugkRCI0BPFjWY2h/cDcM8IiibQ8i/wycUgVk2C+E7gCZkjL1YPxGZdnQkfFgIHXAtPhw2YeKHP+VC6HiZpVrZw== "@lidofinance/lido-ui@^3.18.0": version "3.21.0" @@ -2371,34 +2371,34 @@ ua-parser-js "^1.0.35" use-callback-ref "1.2.5" -"@lidofinance/next-api-wrapper@^0.45.0", "@lidofinance/next-api-wrapper@~0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/next-api-wrapper/-/next-api-wrapper-0.45.0.tgz#d97a225333393af9cf7bd71246ffd12e06b24b0c" - integrity sha512-vkpMRasRq3GWqK2QhlMWDKHPNSCFBbkqWZ9BSdeutWsfyHr1vgQ48u+bKQP5plkTviIFwFJ/Otw+qyCQcay3lA== +"@lidofinance/next-api-wrapper@^0.45.1", "@lidofinance/next-api-wrapper@~0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/next-api-wrapper/-/next-api-wrapper-0.45.1.tgz#59fe3c654c9276eaef8674302470fcc7bb9d8211" + integrity sha512-SUAc520aooNwNJZ0Q1391Yz8iONPiVMQrRC0wWXWDETMFy0ZhCsQ5VCuRKf6rmZwxYjK7K600GV8ke06IIetaw== -"@lidofinance/next-ip-rate-limit@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/next-ip-rate-limit/-/next-ip-rate-limit-0.45.0.tgz#09d53e4386d6d9f65541b141b9419a6139c15594" - integrity sha512-WoS91RrJhsgkWMM/YSGlG5hRnlOpy05j+bW/Z2zj+5V1968yzFg/nlYn7cJ8fdL1GC4PmG+KeKqnwIVdmTozWw== +"@lidofinance/next-ip-rate-limit@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/next-ip-rate-limit/-/next-ip-rate-limit-0.45.1.tgz#79ab181e9f125ad79c46a642dbeec5d8a194397c" + integrity sha512-8d0l6zi4IqX08Op5UCnGM5Z76tkX6GjzzfyjD6Mt7cNt/eNS8AGGKK9wbhUGmumu97l5L0hTZgEW3rNxA8+ptg== dependencies: - "@lidofinance/next-api-wrapper" "~0.45.0" + "@lidofinance/next-api-wrapper" "~0.45.1" -"@lidofinance/next-pages@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/next-pages/-/next-pages-0.45.0.tgz#45ec0441e74ced4c4f8d5b277c3f07b96513d653" - integrity sha512-64DvtGpSYSTscodFAem+gZ/1OvfkPrSNPV7Rbc3hY83vQj3lZ+DKhDBELv6pNOBh8KWvKSB2354vcNeuhKLuNg== +"@lidofinance/next-pages@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/next-pages/-/next-pages-0.45.1.tgz#b4c8fe555ba8f04b9cb2fe88a258632fe3e8552a" + integrity sha512-eaqZ2LUWsQg+lOhdzdL+WGQ3h5p7R+HYJ78q3UhnhWMrwZkMDNgxX6dWw7TudcD1yVC9yeZeT9tpfe7Hbd/XEQ== -"@lidofinance/rpc@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/rpc/-/rpc-0.45.0.tgz#40fadc5d6d9716c0a7c76ec0754b56a1e438f46d" - integrity sha512-oNXgOvLTAPPn+3TyIhuLMLNCJC85AgCUL4G1dHf2KBmlCKVyQzx5tITKKIGJ2GIdPWxFtjqtT+kBMQ51v0Ed7w== +"@lidofinance/rpc@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/rpc/-/rpc-0.45.1.tgz#53e2717ac434c4467ba0135d5144b64cb7af5548" + integrity sha512-F3ZgvBqM9sm8sQEiqGRO05fwBV/ZaexT4RmeSp3I6NcBtpaNuTf8z8IMDcslDhgk7HZZHt/VIAY+8TgVqV8jXw== dependencies: isomorphic-fetch "^3.0.0" -"@lidofinance/satanizer@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@lidofinance/satanizer/-/satanizer-0.45.0.tgz#9bb379accbfc3a0eb70f0af85fb45e8d1519fba9" - integrity sha512-m0kcCQADxl7sl7CoWJWFgwpBOI19HHY38oDxVfPYGhfnT+lJN1N20bZe9YKTwAXXowh+uDSO+NCFy86I+P84IQ== +"@lidofinance/satanizer@^0.45.1": + version "0.45.1" + resolved "https://registry.yarnpkg.com/@lidofinance/satanizer/-/satanizer-0.45.1.tgz#22fe644d7657b333074e9806e2b4229d210ba3a7" + integrity sha512-RC9jKEKykFhHFHUJNziFzvTP4LEIr1idRHgK96myKOn2w5GsiZkTd6SiaI7H9/X+Rg9ZbnTb0zcLZdYQPG946A== "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.1.2" From 105b16752c35f180531785c83276b4c155dbd37e Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Thu, 5 Sep 2024 19:29:07 +0700 Subject: [PATCH 15/16] fix: sdk package --- package.json | 1 + yarn.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/package.json b/package.json index b75e90ed5..79c3ddb37 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@lidofinance/api-rpc": "^0.45.1", "@lidofinance/eth-api-providers": "^0.45.1", "@lidofinance/eth-providers": "^0.45.1", + "@lidofinance/lido-ethereum-sdk": "^3.4.0", "@lidofinance/lido-ui": "^3.26.0", "@lidofinance/next-api-wrapper": "^0.45.1", "@lidofinance/next-ip-rate-limit": "^0.45.1", diff --git a/yarn.lock b/yarn.lock index 422beab7d..cf3a92077 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1821,6 +1821,11 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@graphql-typed-document-node/core@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" + integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -2343,6 +2348,16 @@ resolved "https://registry.yarnpkg.com/@lidofinance/eth-providers/-/eth-providers-0.45.1.tgz#54cbd893c92c06f7ad605ebe0fd8059fc326f180" integrity sha512-ugkRCI0BPFjWY2h/cDcM8IiibQ8i/wycUgVk2C+E7gCZkjL1YPxGZdnQkfFgIHXAtPhw2YeKHP+VC6HiZpVrZw== +"@lidofinance/lido-ethereum-sdk@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@lidofinance/lido-ethereum-sdk/-/lido-ethereum-sdk-3.4.0.tgz#8da4e41bf5d045e50017345bf12a363c17a5273d" + integrity sha512-dHCRhDah9QOlgZgf+GzMMEpE3wPEfudoczeuThW5kTFIUygmww1fPeV8y3HnOip5wpP/4dT4p3/ABCcy0bO0pw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + graphql "^16.8.1" + graphql-request "^6.1.0" + viem "^2.0.6" + "@lidofinance/lido-ui@^3.18.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lidofinance/lido-ui/-/lido-ui-3.21.0.tgz#da772e44ca7e96a062187858fe896ebc7e3d5cd1" @@ -2749,6 +2764,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@~1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -3127,6 +3147,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== +"@scure/base@~1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" + integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== + "@scure/bip32@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" @@ -3144,6 +3169,14 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip39@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + "@sinclair/typebox@^0.24.1": version "0.24.51" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" @@ -6808,6 +6841,19 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +graphql-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f" + integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw== + dependencies: + "@graphql-typed-document-node/core" "^3.2.0" + cross-fetch "^3.1.5" + +graphql@^16.8.1: + version "16.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" + integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== + gray-matter@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" @@ -11401,6 +11447,21 @@ viem@2.18.8: webauthn-p256 "0.0.5" ws "8.17.1" +viem@^2.0.6: + version "2.21.2" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.2.tgz#8e6cae0babd12edee745e551b437d9e38985a97f" + integrity sha512-gTzwKbmyepEDUBKXs3GslTcg5KXfDIgQfHKNxIV9cs7Xout55F8NvHhNeBGBfuw1Ix4Vz8aCMFGYwX5a64CGFg== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.4.0" + abitype "1.0.5" + isows "1.0.4" + webauthn-p256 "0.0.5" + ws "8.17.1" + viem@^2.1.1: version "2.17.9" resolved "https://registry.yarnpkg.com/viem/-/viem-2.17.9.tgz#40ffd00a31621c8efdc4d49a58d5d30dc2d38d83" From 5b3e5dfdd6f04a610480fe780de880442151824e Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 6 Sep 2024 13:36:39 +0700 Subject: [PATCH 16/16] fix: increase balance cache-time --- shared/hooks/use-balance.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/hooks/use-balance.ts b/shared/hooks/use-balance.ts index 56ebdade8..15184941e 100644 --- a/shared/hooks/use-balance.ts +++ b/shared/hooks/use-balance.ts @@ -196,7 +196,13 @@ const useTokenBalance = ( address: contract?.address, functionName: 'balanceOf', args: address && [address], - query: { enabled: !!address, select: nativeToBN }, + query: { + enabled: !!address, + select: nativeToBN, + // because we update on events we can have high staleTime + // this prevents loader when changing pages + staleTime: 30_000, + }, }); useEffect(() => { @@ -225,6 +231,7 @@ export const useStethBalance = ({ const { data: contract, isLoading } = useQuery({ queryKey: ['steth-contract', core.chainId], enabled: !!mergedAccount, + staleTime: Infinity, queryFn: async () => steth.getContract(), });