diff --git a/package.json b/package.json index 3a72d0e..e4aa1af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stability-ui", "type": "module", - "version": "0.13.15-alpha", + "version": "0.13.17-alpha", "scripts": { "dev": "astro dev", "start": "astro dev", diff --git a/src/layouts/AppStore.tsx b/src/layouts/AppStore.tsx index 73bde6c..122f54b 100644 --- a/src/layouts/AppStore.tsx +++ b/src/layouts/AppStore.tsx @@ -38,8 +38,6 @@ import { import { wagmiConfig, platforms, PlatformABI, IVaultManagerABI } from "@web3"; -import { ErrorMessage } from "@ui"; - import { calculateAPY, getStrategyInfo, @@ -100,7 +98,8 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { const $lastTx = useStore(lastTx); const $reload = useStore(reload); - const $error = useStore(error); + + let isError = false; const localVaults: { [network: string]: TVaults; @@ -110,6 +109,11 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { let stabilityAPIData: TAPIData = {}; + const handleError = (errType: string, description: string) => { + error.set({ state: true, type: errType, description }); + isError = true; + }; + const getDataFromStabilityAPI = async () => { const maxRetries = 3; let currentRetry = 0; @@ -121,11 +125,7 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { stabilityAPIData = response.data; if (stabilityAPIData?.error) { - error.set({ - state: true, - type: "API", - description: stabilityAPIData?.error, - }); + handleError("API", stabilityAPIData?.error); return; } @@ -143,11 +143,7 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { await new Promise((resolve) => setTimeout(resolve, 1000)); } else { console.error("API error:", err); - error.set({ - state: true, - type: "API", - description: err?.message, - }); + handleError("API", err); } } } @@ -697,6 +693,7 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { isVaultsLoaded.set(true); } catch (txError: any) { console.log("BLOCKCHAIN ERROR:", txError); + error.set({ state: true, type: "WEB3", @@ -724,7 +721,9 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { await getDataFromStabilityAPI(); - getData(); + if (!isError) { + getData(); + } if (chain?.id) { currentChainID.set(String(chain?.id)); @@ -739,14 +738,6 @@ const AppStore = (props: React.PropsWithChildren): JSX.Element => { fetchAllData(); }, [address, chain?.id, isConnected, $lastTx, $reload]); - if ($error.state && $error.type === "API") { - return ( -
- -
- ); - } - return (
{props.children}
diff --git a/src/modules/Platform/components/Chains/Chain.tsx b/src/modules/Platform/components/Chains/Chain.tsx index 2c8c8a3..ee98178 100644 --- a/src/modules/Platform/components/Chains/Chain.tsx +++ b/src/modules/Platform/components/Chains/Chain.tsx @@ -1,3 +1,5 @@ +import { useMemo } from "react"; + import { type ApiMainReply, assets, @@ -11,7 +13,7 @@ import { import { useStore } from "@nanostores/react"; -import { apiData } from "@store"; +import { apiData, vaults } from "@store"; import { formatNumber } from "@utils"; @@ -21,7 +23,7 @@ import { ChainStatus, StrategyStatus } from "../../ui"; import tokenlist from "@stabilitydao/stability/out/stability.tokenlist.json"; -import type { TStrategyState } from "@types"; +import type { TStrategyState, TVault } from "@types"; interface IProps { chain: number; @@ -29,6 +31,7 @@ interface IProps { const Chain: React.FC = ({ chain }) => { const $apiData: ApiMainReply | undefined = useStore(apiData); + const $vaults = useStore(vaults); const chainData = { ...chains[chain], @@ -69,10 +72,45 @@ const Chain: React.FC = ({ chain }) => { }, { name: "TVL", - content: `\$${formatNumber($apiData?.total.chainTvl[chain.toString()] ? $apiData?.total.chainTvl[chain.toString()].toFixed(0) : "-", "withSpaces")}`, + content: `${formatNumber($apiData?.total.chainTvl[chain.toString()] ? $apiData?.total.chainTvl[chain.toString()].toFixed(0) : "-", "abbreviate")}`, }, ]; + const vaultsInfo = useMemo(() => { + if (!$apiData) return []; + + const chainVaults: TVault[] = Object.entries($vaults[chain] || {}).map( + (vault) => vault[1] as TVault + ); + + const vaultsTVL: number = chainVaults.reduce( + (acc: number, cur) => (acc += Number(cur?.tvl)), + 0 + ); + + const weightedAverageAPR: number = chainVaults.reduce( + (acc: number, cur) => + acc + + (Number(cur?.earningData?.apr?.daily) * Number(cur?.tvl)) / vaultsTVL, + 0 + ); + + return [ + { + name: "Vaults", + content: chainVaults.length, + }, + { + name: "APR", + content: `${weightedAverageAPR.toFixed(2)}%`, + }, + { + name: "Vaults TVL", + content: formatNumber(vaultsTVL, "abbreviate"), + }, + ]; + }, [$vaults, chain]); + const chainAssets = assets.filter((asset) => Object.keys(asset.addresses).includes(chain.toString()) ); @@ -109,6 +147,36 @@ const Chain: React.FC = ({ chain }) => { ))} + {chainData.status === "SUPPORTED" && vaultsInfo.length ? ( +
+ {vaultsInfo.map(({ name, content }, index) => ( +
+ {!index ? ( + + {content} + + ) : ( +
+ {content} +
+ )} + +
+ {name} +
+
+ ))} +
+ ) : ( + "" + )} {strategies.length > 0 && ( diff --git a/src/modules/Vault/components/HistoricalRate/index.tsx b/src/modules/Vault/components/HistoricalRate/index.tsx index 16000b8..fc24619 100644 --- a/src/modules/Vault/components/HistoricalRate/index.tsx +++ b/src/modules/Vault/components/HistoricalRate/index.tsx @@ -18,6 +18,7 @@ import type { TAddress, TChartData } from "@types"; interface IProps { network: string; address: TAddress; + created: number; vaultStrategy: string; lastHardWork: number; } @@ -31,7 +32,7 @@ type TActiveChart = | undefined; const HistoricalRate: React.FC = memo( - ({ network, address, vaultStrategy, lastHardWork }) => { + ({ network, address, created, vaultStrategy, lastHardWork }) => { const APRType = vaultStrategy === "Compound Farm" ? "APR" : "Farm APR"; const timelineSegments = { @@ -55,6 +56,8 @@ const HistoricalRate: React.FC = memo( errorMessage: "", }); + const createdDaysDifference = getTimeDifference(created)?.days; + const formatData = (obj: TChartData) => { const date = new Date(Number(obj.timestamp) * 1000); @@ -81,8 +84,6 @@ const HistoricalRate: React.FC = memo( const NOW = Math.floor(Date.now() / 1000); const DATA = []; - let newData = []; - let time = NOW - TIMESTAMPS_IN_SECONDS.WEEK; let entities = 0; let status = true; @@ -90,7 +91,7 @@ const HistoricalRate: React.FC = memo( while (status) { const HISTORY_QUERY = `{ vaultHistoryEntities( - orderBy: timestamp, + orderBy: timestamp, orderDirection: asc, where: {address: "${address}", periodVsHoldAPR_not: null} skip: ${entities} @@ -117,55 +118,17 @@ const HistoricalRate: React.FC = memo( const workedData = DATA.map(formatData); - let APRChartData = workedData - .filter( - (obj) => - obj.APR && obj.unixTimestamp >= NOW - TIMESTAMPS_IN_SECONDS.WEEK - ) - .map((obj) => ({ - unixTimestamp: obj.unixTimestamp, - timestamp: obj.timestamp, - date: obj.date, - APR: formatFromBigInt(obj.APR, 3, "withDecimals"), - APR24H: obj.APR24H, - vsHoldAPR: Number(obj.periodVsHoldAPR).toFixed(3), - })); + let _chartData = workedData.filter( + (obj) => + obj.APR && obj.unixTimestamp >= NOW - TIMESTAMPS_IN_SECONDS.WEEK + ); - if (!APRChartData.length) { + if (!_chartData.length) { setIsData(false); return; } - const lastTimestamp = - APRChartData[APRChartData.length - 1].unixTimestamp; - - do { - let sortedAPRs = APRChartData.filter( - (obj) => obj.unixTimestamp >= time - ); - - let firstEl = sortedAPRs[0] || APRChartData[APRChartData.length - 1]; - - newData.push({ ...firstEl, timestamp: time }); - time += 3600; - if (time >= lastTimestamp) { - newData.push({ - ...APRChartData[APRChartData.length - 1], - timestamp: APRChartData[APRChartData.length - 1].unixTimestamp, - }); - } - } while (time < lastTimestamp); - - APRChartData = newData.map(formatData); - - if (daysFromLastHardWork >= 3) { - APRChartData = APRChartData.filter( - (data) => data.unixTimestamp < lastHardWork - ); - } - setChartData(workedData); - setActiveChart({ name: "APR", data: APRChartData as [] }); } catch (error) { const err = error as AxiosError; if (err.response) { @@ -190,8 +153,9 @@ const HistoricalRate: React.FC = memo( const NOW = Math.floor(Date.now() / 1000); const TIME: number = TIMESTAMPS_IN_SECONDS[segment]; - let time = NOW - TIME, - newData; + let time = NOW - TIME; + + let newData; const lastTimestamp = chartData[chartData.length - 1].unixTimestamp; @@ -203,24 +167,25 @@ const HistoricalRate: React.FC = memo( newData = []; - if (segment === "MONTH") { + if (segment === "WEEK" && createdDaysDifference >= 7) { do { let sortedAPRs = APRArr.filter( (obj) => obj.unixTimestamp >= time ); + let firstEl = sortedAPRs[0] || APRArr[APRArr.length - 1]; newData.push({ ...firstEl, timestamp: time }); - time += 7200; + time += 3600; + if (time >= lastTimestamp) { newData.push({ ...APRArr[APRArr.length - 1], - timestamp: APRArr[APRArr.length - 1].unixTimestamp, + timestamp: APRArr[APRArr.length - 1]?.unixTimestamp, }); } } while (time < lastTimestamp); - } else if (segment === "YEAR") { - time = APRArr[0].unixTimestamp; + } else if (segment === "MONTH" && createdDaysDifference >= 30) { do { let sortedAPRs = APRArr.filter( (obj) => obj.unixTimestamp >= time @@ -228,7 +193,7 @@ const HistoricalRate: React.FC = memo( let firstEl = sortedAPRs[0] || APRArr[APRArr.length - 1]; newData.push({ ...firstEl, timestamp: time }); - time += 14400; + time += 7200; if (time >= lastTimestamp) { newData.push({ ...APRArr[APRArr.length - 1], @@ -237,6 +202,7 @@ const HistoricalRate: React.FC = memo( } } while (time < lastTimestamp); } else { + time = APRArr[0].unixTimestamp; do { let sortedAPRs = APRArr.filter( (obj) => obj.unixTimestamp >= time @@ -244,7 +210,7 @@ const HistoricalRate: React.FC = memo( let firstEl = sortedAPRs[0] || APRArr[APRArr.length - 1]; newData.push({ ...firstEl, timestamp: time }); - time += 3600; + time += 14400; if (time >= lastTimestamp) { newData.push({ ...APRArr[APRArr.length - 1], @@ -293,13 +259,87 @@ const HistoricalRate: React.FC = memo( data: APRChartData as [], }); break; + case "vsHodl": + let vsHoldArr = chartData.filter( + (obj: TChartData) => obj.APR && obj.unixTimestamp >= NOW - TIME + ); + + newData = []; + if (segment === "WEEK" && createdDaysDifference >= 7) { + do { + let sortedAPRs = vsHoldArr.filter( + (obj) => obj.unixTimestamp >= time + ); + let firstEl = sortedAPRs[0] || vsHoldArr[vsHoldArr.length - 1]; + + newData.push({ ...firstEl, timestamp: time }); + time += 3600; + if (time >= lastTimestamp) { + newData.push({ + ...vsHoldArr[vsHoldArr.length - 1], + timestamp: vsHoldArr[vsHoldArr.length - 1].unixTimestamp, + }); + } + } while (time < lastTimestamp); + } else if (segment === "MONTH" && createdDaysDifference >= 30) { + do { + let sortedAPRs = vsHoldArr.filter( + (obj) => obj.unixTimestamp >= time + ); + let firstEl = sortedAPRs[0] || vsHoldArr[vsHoldArr.length - 1]; + + newData.push({ ...firstEl, timestamp: time }); + time += 7200; + if (time >= lastTimestamp) { + newData.push({ + ...vsHoldArr[vsHoldArr.length - 1], + timestamp: vsHoldArr[vsHoldArr.length - 1].unixTimestamp, + }); + } + } while (time < lastTimestamp); + } else { + time = vsHoldArr[0].unixTimestamp; + do { + let sortedAPRs = vsHoldArr.filter( + (obj) => obj.unixTimestamp >= time + ); + let firstEl = sortedAPRs[0] || vsHoldArr[vsHoldArr.length - 1]; + + newData.push({ ...firstEl, timestamp: time }); + time += 14400; + if (time >= lastTimestamp) { + newData.push({ + ...vsHoldArr[vsHoldArr.length - 1], + timestamp: vsHoldArr[vsHoldArr.length - 1].unixTimestamp, + }); + } + } while (time < lastTimestamp); + } + + vsHoldArr = newData.map(formatData); + + const vsHoldAPRChartData = vsHoldArr.map((obj: TChartData) => ({ + unixTimestamp: obj.unixTimestamp, + timestamp: obj.timestamp, + date: obj.date, + vsHodl: Number(obj.periodVsHoldAPR).toFixed(3), + })); + setActiveChart({ + name: "vsHodl", + data: vsHoldAPRChartData as [], + }); + break; case "TVL": let TVLArr = chartData.filter( (obj: TChartData) => obj.unixTimestamp >= NOW - TIME ); newData = []; - if (segment === "YEAR") { + if ( + segment === "YEAR" || + (segment === "WEEK" && createdDaysDifference < 7) || + (segment === "MONTH" && createdDaysDifference < 30) + ) { time = TVLArr[0].unixTimestamp; } @@ -356,7 +396,11 @@ const HistoricalRate: React.FC = memo( newData = []; - if (segment === "YEAR") { + if ( + segment === "YEAR" || + (segment === "WEEK" && createdDaysDifference < 7) || + (segment === "MONTH" && createdDaysDifference < 30) + ) { time = priceArr[0].unixTimestamp; } @@ -411,78 +455,6 @@ const HistoricalRate: React.FC = memo( data: priceChartData as [], }); break; - - case "vsHodl": - let vsHoldArr = chartData.filter( - (obj: TChartData) => obj.APR && obj.unixTimestamp >= NOW - TIME - ); - - newData = []; - - if (segment === "MONTH") { - do { - let sortedAPRs = vsHoldArr.filter( - (obj) => obj.unixTimestamp >= time - ); - let firstEl = sortedAPRs[0] || vsHoldArr[vsHoldArr.length - 1]; - - newData.push({ ...firstEl, timestamp: time }); - time += 7200; - if (time >= lastTimestamp) { - newData.push({ - ...vsHoldArr[vsHoldArr.length - 1], - timestamp: vsHoldArr[vsHoldArr.length - 1].unixTimestamp, - }); - } - } while (time < lastTimestamp); - } else if (segment === "YEAR") { - time = vsHoldArr[0].unixTimestamp; - do { - let sortedAPRs = vsHoldArr.filter( - (obj) => obj.unixTimestamp >= time - ); - let firstEl = sortedAPRs[0] || vsHoldArr[vsHoldArr.length - 1]; - - newData.push({ ...firstEl, timestamp: time }); - time += 14400; - if (time >= lastTimestamp) { - newData.push({ - ...vsHoldArr[vsHoldArr.length - 1], - timestamp: vsHoldArr[vsHoldArr.length - 1].unixTimestamp, - }); - } - } while (time < lastTimestamp); - } else { - do { - let sortedAPRs = vsHoldArr.filter( - (obj) => obj.unixTimestamp >= time - ); - let firstEl = sortedAPRs[0] || vsHoldArr[vsHoldArr.length - 1]; - - newData.push({ ...firstEl, timestamp: time }); - time += 3600; - if (time >= lastTimestamp) { - newData.push({ - ...vsHoldArr[vsHoldArr.length - 1], - timestamp: vsHoldArr[vsHoldArr.length - 1].unixTimestamp, - }); - } - } while (time < lastTimestamp); - } - - vsHoldArr = newData.map(formatData); - - const vsHoldAPRChartData = vsHoldArr.map((obj: TChartData) => ({ - unixTimestamp: obj.unixTimestamp, - timestamp: obj.timestamp, - date: obj.date, - vsHodl: Number(obj.periodVsHoldAPR).toFixed(3), - })); - setActiveChart({ - name: "vsHodl", - data: vsHoldAPRChartData as [], - }); - break; default: console.log("NO ACTIVE CASE"); break; @@ -495,6 +467,12 @@ const HistoricalRate: React.FC = memo( chartHandler(activeChart?.name || "", segment); }; + useEffect(() => { + if (chartData.length) { + chartHandler("APR", "WEEK"); + } + }, [chartData]); + useEffect(() => { getData(); }, []); diff --git a/src/modules/Vault/index.tsx b/src/modules/Vault/index.tsx index 09ca8e2..c536e9f 100644 --- a/src/modules/Vault/index.tsx +++ b/src/modules/Vault/index.tsx @@ -46,13 +46,6 @@ const Vault: React.FC = ({ network, vault }) => { } }, [$vaults, $vaultData]); - if ($error.state && $error.type === "API") { - return ( -
- -
- ); - } return vault && localVault ? (
@@ -70,6 +63,7 @@ const Vault: React.FC = ({ network, vault }) => { @@ -109,11 +103,13 @@ const Vault: React.FC = ({ network, vault }) => { strategy={localVault?.strategyAddress} />
-
) : ( -
- +
+ {" "} +
+ +
); }; diff --git a/src/modules/Vaults/components/NetworksFilter.tsx b/src/modules/Vaults/components/NetworksFilter.tsx index 544fdb1..7e80209 100644 --- a/src/modules/Vaults/components/NetworksFilter.tsx +++ b/src/modules/Vaults/components/NetworksFilter.tsx @@ -13,7 +13,7 @@ const NetworkFilters: React.FC = memo(
{activeNetworks.map((chain) => (
= memo(({ vaults, tab, setTab }) => { const paginationNumbers = []; + for (let i = 1; i <= Math.ceil(vaults.length / PAGINATION_VAULTS); i++) { paginationNumbers.push(i); } + const VISIBLE_VAULTS = { + first: tab === 1 ? tab : PAGINATION_VAULTS * (tab - 1), + latest: + PAGINATION_VAULTS * tab >= vaults.length + ? vaults.length + : PAGINATION_VAULTS * tab, + }; return ( paginationNumbers.length > 1 && ( -
- {paginationNumbers.map((number) => ( -

setTab(number)} - key={number} - > - {number} -

- ))} +
+
+ {paginationNumbers.map((number) => ( +

setTab(number)} + key={number} + > + {number} +

+ ))} +
+

+ Vaults: {VISIBLE_VAULTS.first}-{VISIBLE_VAULTS.latest} of{" "} + {vaults.length} +

) ); diff --git a/src/modules/Vaults/index.tsx b/src/modules/Vaults/index.tsx index 47fc5f4..b3110e6 100644 --- a/src/modules/Vaults/index.tsx +++ b/src/modules/Vaults/index.tsx @@ -30,6 +30,7 @@ import { isVaultsLoaded, aprFilter, connected, + error, // platformVersions, // currentChainID, // assetsPrices, @@ -94,6 +95,8 @@ const Vaults = (): JSX.Element => { const $isVaultsLoaded = useStore(isVaultsLoaded); const $aprFilter: TAPRPeriod = useStore(aprFilter); const $connected = useStore(connected); + + const $error = useStore(error); // const $publicClient = useStore(publicClient); // const $platformVersions = useStore(platformVersions); // const $currentChainID = useStore(currentChainID); @@ -506,7 +509,7 @@ const Vaults = (): JSX.Element => {
- + {/* {!!platformUpdates?.newVersion && platformUpdates?.newVersion != $platformVersions[$currentChainID] && !!upgradesTable?.length && ( diff --git a/src/types/index.ts b/src/types/index.ts index d19b039..4400032 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -565,6 +565,7 @@ type TAPIData = { }; }; }; + error?: string; }; type TVLRange = { min: number; max: number }; diff --git a/src/ui/ErrorMessage.tsx b/src/ui/ErrorMessage.tsx index 2f77b88..4cce80c 100644 --- a/src/ui/ErrorMessage.tsx +++ b/src/ui/ErrorMessage.tsx @@ -10,22 +10,7 @@ const ErrorMessage: React.FC = ({ type }) => { const $error = useStore(error); const $reload = useStore(reload); - if ($error.state && type === "API") { - return ( -
-
-

{$error.description.slice(0, 40)}...

- -
-
- ); - } - if ($error.state && type === "WEB3") { + if ($error.state) { return (
= ({ type }) => { -

{$error.description.slice(0, 25)}...

+

+ {$error.description.slice(0, type === "WEB3" ? 25 : 40)}... +