From 0b64878b1260df54aebb706ca2c7c94c18b38388 Mon Sep 17 00:00:00 2001 From: David Totraev Date: Tue, 1 Oct 2024 20:31:30 +0500 Subject: [PATCH] feat: refactor global params --- src/app/components/FAQ/FAQ.tsx | 31 +--------- src/app/components/Staking/Staking.tsx | 46 +++++---------- src/app/components/Stats/Stats.tsx | 57 +++++++++--------- src/app/components/Summary/Summary.tsx | 31 ++-------- src/app/constants.ts | 2 + src/app/context/api/GlobalParamsProvider.tsx | 43 -------------- src/app/context/api/VersionInfo.tsx | 62 ++++++++++++++++++++ src/app/hooks/useApi.ts | 41 +++++++++++++ src/app/hooks/useBTCTipHeight.ts | 11 ++++ src/app/hooks/useNetworkFees.ts | 14 +++++ src/app/hooks/useVersions.ts | 14 +++++ src/app/page.tsx | 52 +++------------- src/app/providers.tsx | 6 +- src/utils/globalParams.ts | 4 +- 14 files changed, 205 insertions(+), 209 deletions(-) create mode 100644 src/app/constants.ts delete mode 100644 src/app/context/api/GlobalParamsProvider.tsx create mode 100644 src/app/context/api/VersionInfo.tsx create mode 100644 src/app/hooks/useApi.ts create mode 100644 src/app/hooks/useBTCTipHeight.ts create mode 100644 src/app/hooks/useNetworkFees.ts create mode 100644 src/app/hooks/useVersions.ts diff --git a/src/app/components/FAQ/FAQ.tsx b/src/app/components/FAQ/FAQ.tsx index aecf825b..b842d185 100644 --- a/src/app/components/FAQ/FAQ.tsx +++ b/src/app/components/FAQ/FAQ.tsx @@ -1,12 +1,5 @@ -import { useEffect, useState } from "react"; - -import { useGlobalParams } from "@/app/context/api/GlobalParamsProvider"; -import { useBtcHeight } from "@/app/context/mempool/BtcHeightProvider"; +import { useVersionInfo } from "@/app/context/api/VersionInfo"; import { getNetworkConfig } from "@/config/network.config"; -import { - getCurrentGlobalParamsVersion, - ParamsWithContext, -} from "@/utils/globalParams"; import { questions } from "./data/questions"; import { Section } from "./Section"; @@ -14,26 +7,8 @@ import { Section } from "./Section"; interface FAQProps {} export const FAQ: React.FC = () => { - const [paramWithCtx, setParamWithCtx] = useState< - ParamsWithContext | undefined - >(); const { coinName, networkName } = getNetworkConfig(); - const btcHeight = useBtcHeight(); - const globalParams = useGlobalParams(); - - useEffect(() => { - if (!btcHeight || !globalParams.data) { - return; - } - const paramsWithCtx = getCurrentGlobalParamsVersion( - btcHeight + 1, - globalParams.data, - ); - if (!paramsWithCtx) { - return; - } - setParamWithCtx(paramsWithCtx); - }, [globalParams, btcHeight]); + const versionInfo = useVersionInfo(); return (
@@ -42,7 +17,7 @@ export const FAQ: React.FC = () => { {questions( coinName, networkName, - paramWithCtx?.currentVersion?.confirmationDepth, + versionInfo?.currentVersion?.confirmationDepth, ).map((question) => (
= ({ useLocalStorage("bbn-staking-successFeedbackModalOpened", false); const [cancelFeedbackModalOpened, setCancelFeedbackModalOpened] = useLocalStorage("bbn-staking-cancelFeedbackModalOpened ", false); - const [paramWithCtx, setParamWithCtx] = useState< - ParamsWithContext | undefined - >(); const [overflow, setOverflow] = useState({ isHeightCap: false, overTheCapRange: false, approchingCapRange: false, }); + const versionInfo = useVersionInfo(); + // Mempool fee rates, comes from the network // Fetch fee rates, sat/vB const { @@ -134,26 +129,13 @@ export const Staking: React.FC = ({ const stakingStats = useStakingStats(); - // load global params and calculate the current staking params - const globalParams = useGlobalParams(); - useEffect(() => { - if (!btcHeight || !globalParams.data) { - return; - } - const paramCtx = getCurrentGlobalParamsVersion( - btcHeight + 1, - globalParams.data, - ); - setParamWithCtx(paramCtx); - }, [btcHeight, globalParams]); - // Calculate the overflow properties useEffect(() => { - if (!paramWithCtx || !paramWithCtx.currentVersion || !btcHeight) { + if (!versionInfo?.currentVersion || !btcHeight) { return; } const nextBlockHeight = btcHeight + 1; - const { stakingCapHeight, stakingCapSat } = paramWithCtx.currentVersion; + const { stakingCapHeight, stakingCapSat } = versionInfo.currentVersion; // Use height based cap than value based cap if it is set if (stakingCapHeight) { setOverflow({ @@ -178,12 +160,12 @@ export const Staking: React.FC = ({ stakingCapSat * OVERFLOW_TVL_WARNING_THRESHOLD < unconfirmedTVLSat, }); } - }, [paramWithCtx, btcHeight, stakingStats]); + }, [versionInfo, btcHeight, stakingStats]); const { coinName } = getNetworkConfig(); - const stakingParams = paramWithCtx?.currentVersion; - const firstActivationHeight = paramWithCtx?.firstActivationHeight; - const isUpgrading = paramWithCtx?.isApprochingNextVersion; + const stakingParams = versionInfo?.currentVersion; + const firstActivationHeight = versionInfo?.firstActivationHeight; + const isUpgrading = versionInfo?.isApprochingNextVersion; const isBlockHeightUnderActivation = !stakingParams || (btcHeight && @@ -251,13 +233,13 @@ export const Staking: React.FC = ({ if (!btcWalletNetwork) throw new Error("Wallet network is not connected"); if (!finalityProvider) throw new Error("Finality provider is not selected"); - if (!paramWithCtx || !paramWithCtx.currentVersion) + if (!versionInfo?.currentVersion) throw new Error("Global params not loaded"); if (!feeRate) throw new Error("Fee rates not loaded"); if (!availableUTXOs || availableUTXOs.length === 0) throw new Error("No available balance"); - const { currentVersion: globalParamsVersion } = paramWithCtx; + const { currentVersion: globalParamsVersion } = versionInfo; // Sign the staking transaction const { stakingTxHex, stakingTerm } = await signStakingTx( btcWallet, @@ -341,7 +323,7 @@ export const Staking: React.FC = ({ publicKeyNoCoord && stakingAmountSat && finalityProvider && - paramWithCtx?.currentVersion && + versionInfo?.currentVersion && mempoolFeeRates && availableUTXOs ) { @@ -353,7 +335,7 @@ export const Staking: React.FC = ({ const memoizedFeeRate = selectedFeeRate || defaultFeeRate; // Calculate the staking fee const { stakingFeeSat } = createStakingTx( - paramWithCtx.currentVersion, + versionInfo.currentVersion, stakingAmountSat, stakingTimeBlocks, finalityProvider.btcPk, @@ -395,7 +377,7 @@ export const Staking: React.FC = ({ stakingAmountSat, stakingTimeBlocks, finalityProvider, - paramWithCtx, + versionInfo, mempoolFeeRates, selectedFeeRate, availableUTXOs, diff --git a/src/app/components/Stats/Stats.tsx b/src/app/components/Stats/Stats.tsx index c6bb5ee7..3ad6bd88 100644 --- a/src/app/components/Stats/Stats.tsx +++ b/src/app/components/Stats/Stats.tsx @@ -1,21 +1,17 @@ import Image from "next/image"; -import { Fragment, useEffect, useState } from "react"; +import { Fragment, useEffect, useMemo, useState } from "react"; import { AiOutlineInfoCircle } from "react-icons/ai"; import { Tooltip } from "react-tooltip"; -import { useGlobalParams } from "@/app/context/api/GlobalParamsProvider"; import { StakingStats, useStakingStats, } from "@/app/context/api/StakingStatsProvider"; -import { useBtcHeight } from "@/app/context/mempool/BtcHeightProvider"; +import { useVersionInfo } from "@/app/context/api/VersionInfo"; import { GlobalParamsVersion } from "@/app/types/globalParams"; import { getNetworkConfig } from "@/config/network.config"; import { satoshiToBtc } from "@/utils/btcConversions"; -import { - ParamsWithContext, - getCurrentGlobalParamsVersion, -} from "@/utils/globalParams"; +import { ParamsWithContext } from "@/utils/globalParams"; import { maxDecimals } from "@/utils/maxDecimals"; import confirmedTvl from "./icons/confirmed-tvl.svg"; @@ -83,17 +79,9 @@ export const Stats: React.FC = () => { totalStakers: 0, unconfirmedTVLSat: 0, }); - const [stakingCapText, setStakingCapText] = useState<{ - title: string; - value: string; - }>({ - title: "Staking TVL Cap", - value: "-", - }); const [isLoading, setIsLoading] = useState(true); const stakingStatsProvider = useStakingStats(); - const btcHeight = useBtcHeight(); - const globalParams = useGlobalParams(); + const versionInfo = useVersionInfo(); const { coinName } = getNetworkConfig(); @@ -102,23 +90,32 @@ export const Stats: React.FC = () => { if (stakingStatsProvider.data) { setStakingStats(stakingStatsProvider.data); } - setIsLoading(stakingStatsProvider.isLoading || globalParams.isLoading); - }, [stakingStatsProvider, globalParams]); + setIsLoading( + stakingStatsProvider.isLoading || versionInfo?.isLoading || false, + ); + }, [stakingStatsProvider, versionInfo?.isLoading]); - useEffect(() => { - if (!btcHeight || !globalParams.data) { - return; + const stakingCapText = useMemo(() => { + if (!versionInfo?.currentHeight) { + return { + title: "Staking TVL Cap", + value: "-", + }; } - const paramsWithCtx = getCurrentGlobalParamsVersion( - btcHeight + 1, - globalParams.data, + + const cap = buildStakingCapSection( + coinName, + versionInfo.currentHeight, + versionInfo, ); - if (!paramsWithCtx) { - return; - } - const cap = buildStakingCapSection(coinName, btcHeight, paramsWithCtx); - if (cap) setStakingCapText(cap); - }, [globalParams, btcHeight, stakingStats, coinName]); + + return ( + cap ?? { + title: "Staking TVL Cap", + value: "-", + } + ); + }, [coinName, versionInfo]); const formatter = Intl.NumberFormat("en", { notation: "compact", diff --git a/src/app/components/Summary/Summary.tsx b/src/app/components/Summary/Summary.tsx index baa1ef5c..75872940 100644 --- a/src/app/components/Summary/Summary.tsx +++ b/src/app/components/Summary/Summary.tsx @@ -1,18 +1,12 @@ -import { useEffect, useState } from "react"; import { AiOutlineInfoCircle } from "react-icons/ai"; import { FaBitcoin } from "react-icons/fa"; import { Tooltip } from "react-tooltip"; -import { useGlobalParams } from "@/app/context/api/GlobalParamsProvider"; -import { useBtcHeight } from "@/app/context/mempool/BtcHeightProvider"; +import { useVersionInfo } from "@/app/context/api/VersionInfo"; import { useHealthCheck } from "@/app/hooks/useHealthCheck"; import { shouldDisplayPoints } from "@/config"; import { getNetworkConfig } from "@/config/network.config"; import { satoshiToBtc } from "@/utils/btcConversions"; -import { - getCurrentGlobalParamsVersion, - ParamsWithContext, -} from "@/utils/globalParams"; import { maxDecimals } from "@/utils/maxDecimals"; import { Network } from "@/utils/wallet/wallet_provider"; @@ -32,26 +26,11 @@ export const Summary: React.FC = ({ btcWalletBalanceSat, publicKeyNoCoord, }) => { - const { coinName } = getNetworkConfig(); - const onMainnet = getNetworkConfig().network === Network.MAINNET; - const [paramWithCtx, setParamWithCtx] = useState< - ParamsWithContext | undefined - >(); - - const btcHeight = useBtcHeight(); - const globalParams = useGlobalParams(); const { isApiNormal, isGeoBlocked } = useHealthCheck(); + const versionInfo = useVersionInfo(); - useEffect(() => { - if (!btcHeight || !globalParams.data) { - return; - } - const paramCtx = getCurrentGlobalParamsVersion( - btcHeight + 1, - globalParams.data, - ); - setParamWithCtx(paramCtx); - }, [btcHeight, globalParams]); + const { coinName } = getNetworkConfig(); + const onMainnet = getNetworkConfig().network === Network.MAINNET; return (
@@ -65,7 +44,7 @@ export const Summary: React.FC = ({ diff --git a/src/app/constants.ts b/src/app/constants.ts new file mode 100644 index 00000000..be40975f --- /dev/null +++ b/src/app/constants.ts @@ -0,0 +1,2 @@ +export const ONE_SECOND = 1000; +export const ONE_MINUTE = 60 * ONE_SECOND; diff --git a/src/app/context/api/GlobalParamsProvider.tsx b/src/app/context/api/GlobalParamsProvider.tsx deleted file mode 100644 index baa89e47..00000000 --- a/src/app/context/api/GlobalParamsProvider.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import React, { ReactNode, createContext, useContext } from "react"; - -import { getGlobalParams } from "@/app/api/getGlobalParams"; -import { GlobalParamsVersion } from "@/app/types/globalParams"; - -interface GlobalParamsProviderProps { - children: ReactNode; -} - -interface GlobalParamsContextType { - data: GlobalParamsVersion[] | undefined; - isLoading: boolean; -} - -const defaultContextValue: GlobalParamsContextType = { - data: undefined, - isLoading: true, -}; - -const GlobalParamsContext = - createContext(defaultContextValue); - -export const GlobalParamsProvider: React.FC = ({ - children, -}) => { - const { data, isLoading } = useQuery({ - queryKey: ["API_GLOBAL_PARAMS"], - queryFn: async () => getGlobalParams(), - refetchInterval: 60000, // 1 minute - }); - - return ( - - {children} - - ); -}; - -/* - * Global Params Context. Provides the global params from the API. - */ -export const useGlobalParams = () => useContext(GlobalParamsContext); diff --git a/src/app/context/api/VersionInfo.tsx b/src/app/context/api/VersionInfo.tsx new file mode 100644 index 00000000..91b3f735 --- /dev/null +++ b/src/app/context/api/VersionInfo.tsx @@ -0,0 +1,62 @@ +import { createContext, PropsWithChildren, useContext, useMemo } from "react"; + +import { useBTCTipHeight } from "@/app/hooks/useBTCTipHeight"; +import { useVersions } from "@/app/hooks/useVersions"; +import type { GlobalParamsVersion } from "@/app/types/globalParams"; +import { getCurrentGlobalParamsVersion } from "@/utils/globalParams"; + +interface VersionInfoProps { + currentVersion?: GlobalParamsVersion; + currentHeight?: number; + nextVersion?: GlobalParamsVersion; + isApprochingNextVersion: boolean; + firstActivationHeight: number; + isError?: boolean; + isLoading?: boolean; +} + +const VersionInfoContext = createContext({ + isApprochingNextVersion: false, + firstActivationHeight: 0, +}); + +export function useVersionInfo() { + return useContext(VersionInfoContext); +} + +export function VersionInfoProvider({ children }: PropsWithChildren) { + const { + data: versions, + isError: isVersionError, + isLoading: isVersionLoading, + } = useVersions(); + const { + data: height, + isError: isHeightError, + isLoading: isHeightLoading, + } = useBTCTipHeight(); + + const context = useMemo(() => { + if (!versions || !height) return; + + return { + currentHeight: height, + isError: isHeightError || isVersionError, + isLoading: isVersionLoading || isHeightLoading, + ...getCurrentGlobalParamsVersion(height + 1, versions), + }; + }, [ + versions, + height, + isHeightError, + isVersionError, + isVersionLoading, + isHeightLoading, + ]); + + return ( + + {children} + + ); +} diff --git a/src/app/hooks/useApi.ts b/src/app/hooks/useApi.ts new file mode 100644 index 00000000..6bb468cc --- /dev/null +++ b/src/app/hooks/useApi.ts @@ -0,0 +1,41 @@ +import { + type DefaultError, + type QueryKey, + type UndefinedInitialDataOptions, + useQuery, +} from "@tanstack/react-query"; +import { useEffect } from "react"; + +import { ONE_MINUTE } from "@/app/constants"; +import { useError } from "@/app/context/Error/ErrorContext"; +import { ErrorState } from "@/app/types/errors"; + +export function useAPIQuery< + TQueryFnData = unknown, + TError extends Error = DefaultError, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: UndefinedInitialDataOptions, +) { + const { isErrorOpen, handleError } = useError(); + + const data = useQuery({ + refetchInterval: ONE_MINUTE, + retry: (failureCount) => { + return !isErrorOpen && failureCount <= 3; + }, + ...options, + }); + + useEffect(() => { + handleError({ + error: data.error, + hasError: data.isError, + errorState: ErrorState.SERVER_ERROR, + refetchFunction: data.refetch, + }); + }, [handleError, data?.error, data?.isError, data.refetch]); + + return data; +} diff --git a/src/app/hooks/useBTCTipHeight.ts b/src/app/hooks/useBTCTipHeight.ts new file mode 100644 index 00000000..ab952f39 --- /dev/null +++ b/src/app/hooks/useBTCTipHeight.ts @@ -0,0 +1,11 @@ +import { useAPIQuery } from "@/app/hooks/useApi"; +import { getTipHeight } from "@/utils/mempool_api"; + +export const BTC_TIP_HEIGHT_KEY = "BTC_TIP_HEIGHT"; + +export function useBTCTipHeight() { + return useAPIQuery({ + queryKey: [BTC_TIP_HEIGHT_KEY], + queryFn: getTipHeight, + }); +} diff --git a/src/app/hooks/useNetworkFees.ts b/src/app/hooks/useNetworkFees.ts new file mode 100644 index 00000000..2a4840c5 --- /dev/null +++ b/src/app/hooks/useNetworkFees.ts @@ -0,0 +1,14 @@ +import { useAPIQuery } from "@/app/hooks/useApi"; +import { getNetworkFees } from "@/utils/mempool_api"; + +export const NETWORK_FEES_KEY = "NETWORK_FEES"; + +export function useNetworkFees({ enabled = true }: { enabled?: boolean } = {}) { + const data = useAPIQuery({ + queryKey: [NETWORK_FEES_KEY], + queryFn: getNetworkFees, + enabled, + }); + + return data; +} diff --git a/src/app/hooks/useVersions.ts b/src/app/hooks/useVersions.ts new file mode 100644 index 00000000..e076a867 --- /dev/null +++ b/src/app/hooks/useVersions.ts @@ -0,0 +1,14 @@ +import { getGlobalParams } from "@/app/api/getGlobalParams"; +import { useAPIQuery } from "@/app/hooks/useApi"; + +export const VARSIONS_KEY = "VARSIONS"; + +export function useVersions({ enabled = true }: { enabled?: boolean } = {}) { + const data = useAPIQuery({ + queryKey: [VARSIONS_KEY], + queryFn: getGlobalParams, + enabled, + }); + + return data; +} diff --git a/src/app/page.tsx b/src/app/page.tsx index ee2e0b95..41134c25 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,14 +6,12 @@ import { useEffect } from "react"; import { useLocalStorage } from "usehooks-ts"; import { network } from "@/config/network.config"; -import { getCurrentGlobalParamsVersion } from "@/utils/globalParams"; import { calculateDelegationsDiff } from "@/utils/local_storage/calculateDelegationsDiff"; import { getDelegationsLocalStorageKey } from "@/utils/local_storage/getDelegationsLocalStorageKey"; import { filterOrdinals } from "@/utils/utxo"; import { Network } from "@/utils/wallet/wallet_provider"; import { getDelegations, PaginatedDelegations } from "./api/getDelegations"; -import { getGlobalParams } from "./api/getGlobalParams"; import { UTXO_KEY } from "./common/constants"; import { signPsbtTransaction } from "./common/utils/psbt"; import { Delegations } from "./components/Delegations/Delegations"; @@ -24,6 +22,7 @@ import { NetworkBadge } from "./components/NetworkBadge/NetworkBadge"; import { Staking } from "./components/Staking/Staking"; import { Stats } from "./components/Stats/Stats"; import { Summary } from "./components/Summary/Summary"; +import { useVersionInfo } from "./context/api/VersionInfo"; import { useError } from "./context/Error/ErrorContext"; import { useWallet } from "./context/wallet/WalletProvider"; import { Delegation, DelegationState } from "./types/delegations"; @@ -41,33 +40,7 @@ const Home: React.FC = () => { const { isErrorOpen, showError, handleError } = useError(); - const { - data: paramWithContext, - isLoading: isLoadingCurrentParams, - error: globalParamsVersionError, - isError: hasGlobalParamsVersionError, - refetch: refetchGlobalParamsVersion, - } = useQuery({ - queryKey: ["global params"], - queryFn: async () => { - const [height, versions] = await Promise.all([ - btcWallet!.getBTCTipHeight(), - getGlobalParams(), - ]); - return { - // The staking parameters are retrieved based on the current height + 1 - // so this verification should take this into account. - currentHeight: height, - nextBlockParams: getCurrentGlobalParamsVersion(height + 1, versions), - }; - }, - refetchInterval: 60000, // 1 minute - // Should be enabled only when the wallet is connected - enabled: !!btcWallet, - retry: (failureCount, error) => { - return !isErrorOpen && failureCount <= 3; - }, - }); + const versionInfo = useVersionInfo(); const { data: delegations, @@ -141,12 +114,6 @@ const Home: React.FC = () => { errorState: ErrorState.SERVER_ERROR, refetchFunction: refetchDelegationData, }); - handleError({ - error: globalParamsVersionError, - hasError: hasGlobalParamsVersionError, - errorState: ErrorState.SERVER_ERROR, - refetchFunction: refetchGlobalParamsVersion, - }); handleError({ error: availableUTXOsError, hasError: hasAvailableUTXOsError, @@ -154,12 +121,9 @@ const Home: React.FC = () => { refetchFunction: refetchAvailableUTXOs, }); }, [ - hasGlobalParamsVersionError, hasDelegationsError, delegationsError, refetchDelegationData, - globalParamsVersionError, - refetchGlobalParamsVersion, showError, availableUTXOsError, hasAvailableUTXOsError, @@ -239,23 +203,21 @@ const Home: React.FC = () => { /> )} {btcWallet && delegations && - paramWithContext?.nextBlockParams.currentVersion && + versionInfo?.currentVersion && btcWalletNetwork && ( - + @@ -28,7 +28,7 @@ function Providers({ children }: React.PropsWithChildren) { - +