From 8ae160087a03f043cfa6b6a703ee24e8106f2f02 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 16 Oct 2024 18:53:14 +0100 Subject: [PATCH] Fix sFUEL status checks, add sFUEL completion percentage --- src/Router.tsx | 7 +- src/components/GetSFuel.tsx | 26 ++++-- src/pages/App.tsx | 8 +- src/pages/Ecosystem.tsx | 8 +- src/useSFuel.tsx | 155 ++++++++++++++++++++++++++---------- 5 files changed, 138 insertions(+), 66 deletions(-) diff --git a/src/Router.tsx b/src/Router.tsx index fd872bd..61d0a8e 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -72,12 +72,7 @@ import MetricsWarning from './components/MetricsWarning' import ScrollToTop from './components/ScrollToTop' import { getHistoryFromStorage, setHistoryToStorage } from './core/transferHistory' -import { - BRIDGE_PAGES, - MAINNET_CHAIN_NAME, - STAKING_PAGES, - STATS_API -} from './core/constants' +import { BRIDGE_PAGES, MAINNET_CHAIN_NAME, STAKING_PAGES, STATS_API } from './core/constants' import { type IValidator, type ISkaleContractsMap, type StakingInfoMap } from './core/interfaces' import { getValidators } from './core/delegation/validators' import { initContracts } from './core/contracts' diff --git a/src/components/GetSFuel.tsx b/src/components/GetSFuel.tsx index 61d0b3a..5364d52 100644 --- a/src/components/GetSFuel.tsx +++ b/src/components/GetSFuel.tsx @@ -23,13 +23,21 @@ import { Box, Button, Tooltip } from '@mui/material' import BoltRoundedIcon from '@mui/icons-material/BoltRounded' +import AutoModeRoundedIcon from '@mui/icons-material/AutoModeRounded' import { cls, styles, cmn, type MetaportCore, useWagmiAccount } from '@skalenetwork/metaport' import { usesFuel } from '../useSFuel' export default function GetSFuel({ mpc }: { mpc: MetaportCore }) { - const { sFuelOk, isMining, mineSFuel } = usesFuel(mpc) + const { sFuelOk, isMining, mineSFuel, sFuelCompletionPercentage, loading } = usesFuel(mpc) const { address } = useWagmiAccount() if (!address) return null + + function btnText() { + if (isMining) return `Getting sFUEL - ${sFuelCompletionPercentage}%` + if (loading) return 'Checking sFUEL' + return sFuelOk ? 'sFUEL OK' : 'Get sFUEL' + } + return ( diff --git a/src/pages/App.tsx b/src/pages/App.tsx index f61d20f..cc45a9e 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -84,13 +84,7 @@ export default function App(props: { chainsMeta: types.ChainsMetadataMap }) { let { chain, app } = useParams() - const { - likedApps, - appLikes, - toggleLikedApp, - getAppId, - refreshLikedApps - } = useLikedApps() + const { likedApps, appLikes, toggleLikedApp, getAppId, refreshLikedApps } = useLikedApps() const { isSignedIn, handleSignIn } = useAuth() const { address } = useWagmiAccount() diff --git a/src/pages/Ecosystem.tsx b/src/pages/Ecosystem.tsx index 4064d19..a29072e 100644 --- a/src/pages/Ecosystem.tsx +++ b/src/pages/Ecosystem.tsx @@ -121,11 +121,11 @@ export default function Ecosystem(props: { 3, isSignedIn ? favoriteApps.filter((app) => - filteredApps.some( - (filteredApp) => - filteredApp.chain === app.chain && filteredApp.appName === app.appName + filteredApps.some( + (filteredApp) => + filteredApp.chain === app.chain && filteredApp.appName === app.appName + ) ) - ) : [] ] // Favorite Apps ]) diff --git a/src/useSFuel.tsx b/src/useSFuel.tsx index 466b7c2..e47b481 100644 --- a/src/useSFuel.tsx +++ b/src/useSFuel.tsx @@ -21,56 +21,83 @@ * @copyright SKALE Labs 2024-Present */ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback, useMemo } from 'react' import { Logger, type ILogObj } from 'tslog' import { useWagmiAccount, type MetaportCore, Station } from '@skalenetwork/metaport' import { DEFAULT_MIN_SFUEL_WEI, SFUEL_CHECK_INTERVAL } from './core/constants' +import { types } from '@/core' const log = new Logger({ name: 'useSFuel' }) const CHAINS_TO_SKIP = ['turbulent-unique-scheat'] // todo: tmp fix, remove later +interface SFuelState { + sFuelOk: boolean + isMining: boolean + chainsWithFaucet: string[] + totalChainsWithStation: number + chainsWithEnoughSFuel: number + currentAddress: types.AddressType | null + loading: boolean + intervalId: number | null +} + export function usesFuel(mpc: MetaportCore) { const { address } = useWagmiAccount() - const [state, setState] = useState({ + const [state, setState] = useState({ sFuelOk: true, isMining: false, - chainsWithFaucet: [] as string[] + chainsWithFaucet: [] as string[], + totalChainsWithStation: 0, + chainsWithEnoughSFuel: 0, + currentAddress: null, + loading: true, + intervalId: null }) - useEffect(() => { - async function checkFaucetAvailability() { - const chainsWithFaucet = await Promise.all( - mpc.config.chains - .filter((chain) => !CHAINS_TO_SKIP.includes(chain)) - .map(async (chain) => { - const station = new Station(chain, mpc) - return (await station.isFaucetAvailable()) ? chain : null - }) - ).then((chains) => chains.filter((chain): chain is string => chain !== null)) - setState((prev) => ({ ...prev, chainsWithFaucet })) - } - checkFaucetAvailability() + const checkFaucetAvailability = useCallback(async () => { + const chainsWithFaucet = await Promise.all( + mpc.config.chains + .filter((chain) => !CHAINS_TO_SKIP.includes(chain)) + .map(async (chain) => { + const station = new Station(chain, mpc) + return (await station.isFaucetAvailable()) ? chain : null + }) + ).then((chains) => chains.filter((chain): chain is string => chain !== null)) + setState((prev) => ({ + ...prev, + chainsWithFaucet, + totalChainsWithStation: chainsWithFaucet.length + })) }, [mpc.config.chains, mpc.config.skaleNetwork]) - async function checkSFuelBalance(): Promise { - if (!address) return true - for (const chain of state.chainsWithFaucet) { - if (CHAINS_TO_SKIP.includes(chain)) continue - const { balance } = await new Station(chain, mpc).getData(address) - if (balance < DEFAULT_MIN_SFUEL_WEI) { - setState((prev) => ({ ...prev, sFuelOk: false })) - return false + const checkSFuelBalance = useCallback( + async (currentAddress: types.AddressType): Promise => { + if (!currentAddress || state.chainsWithFaucet.length === 0) return + if (state.currentAddress !== currentAddress) { + setState((prev) => ({ ...prev, currentAddress, loading: true })) } - } - setState((prev) => ({ ...prev, sFuelOk: true })) - return true - } + let chainsWithEnoughSFuel = 0 + let sFuelOk = true + for (const chain of state.chainsWithFaucet) { + if (CHAINS_TO_SKIP.includes(chain)) continue + const { balance } = await new Station(chain, mpc).getData(currentAddress) + if (balance >= DEFAULT_MIN_SFUEL_WEI) { + chainsWithEnoughSFuel++ + } else { + sFuelOk = false + } + } + setState((prev) => ({ ...prev, sFuelOk, chainsWithEnoughSFuel, loading: false })) + }, + [state.chainsWithFaucet, mpc] + ) - const mineSFuel = async () => { + const mineSFuel = useCallback(async () => { if (!address) return setState((prev) => ({ ...prev, isMining: true })) let errorOccurred = false + let chainsWithEnoughSFuel = 0 for (const chain of state.chainsWithFaucet) { if (CHAINS_TO_SKIP.includes(chain)) continue @@ -83,12 +110,20 @@ export function usesFuel(mpc: MetaportCore) { if (!powResult.ok) { log.error(`Failed to mine sFuel on chain ${chain}: ${powResult.message}`) errorOccurred = true + } else { + chainsWithEnoughSFuel++ } + } else { + chainsWithEnoughSFuel++ } } catch (error) { log.error(`Error processing chain ${chain}:`, error) errorOccurred = true } + setState((prev) => ({ + ...prev, + chainsWithEnoughSFuel + })) } if (errorOccurred) { @@ -97,24 +132,60 @@ export function usesFuel(mpc: MetaportCore) { log.info('sFuel mining completed successfully on all required chains') } - setState((prev) => ({ ...prev, sFuelOk: !errorOccurred, isMining: false })) - } + setState((prev) => ({ + ...prev, + sFuelOk: !errorOccurred, + isMining: false, + chainsWithEnoughSFuel + })) + }, [address, state.chainsWithFaucet, mpc]) useEffect(() => { - if (!address) return - let intervalId: NodeJS.Timeout + checkFaucetAvailability() + }, [checkFaucetAvailability]) - async function checkAndSetInterval() { - await checkSFuelBalance() - intervalId = setInterval(checkSFuelBalance, SFUEL_CHECK_INTERVAL) + useEffect(() => { + const checkAndSetInterval = async () => { + if (address !== state.currentAddress && state.intervalId) { + clearInterval(state.intervalId) + setState((prev) => ({ + ...prev, + intervalId: null + })) + } + if (address) { + await checkSFuelBalance(address) + setState((prev) => ({ ...prev, intervalId: newIntervalId })) + const newIntervalId = setInterval(checkSFuelBalance, SFUEL_CHECK_INTERVAL) + } else { + setState((prev) => ({ + ...prev, + sFuelOk: true, + chainsWithEnoughSFuel: 0, + isChecking: false, + currentAddress: null, + intervalId: null + })) + } } - checkAndSetInterval() - return () => { - if (intervalId) clearInterval(intervalId) + if (state.intervalId) { + clearInterval(state.intervalId) + } } - }, [address, state.chainsWithFaucet]) - - return { ...state, mineSFuel } + }, [address, checkSFuelBalance]) + + const sFuelCompletionPercentage = useMemo(() => { + if (state.totalChainsWithStation === 0) return 100 + return Math.round((state.chainsWithEnoughSFuel / state.totalChainsWithStation) * 100) + }, [state.chainsWithEnoughSFuel, state.totalChainsWithStation]) + + return { + ...state, + mineSFuel, + totalChainsWithStation: state.chainsWithFaucet.length, + chainsWithEnoughSFuel: state.chainsWithEnoughSFuel, + sFuelCompletionPercentage + } }