diff --git a/apps/frontend/.env.example b/apps/frontend/.env.example
index 30a2abf58..24959e971 100644
--- a/apps/frontend/.env.example
+++ b/apps/frontend/.env.example
@@ -1,3 +1,5 @@
+REACT_APP_RATES_HISTORY_API_URL=https://bob-mm-cache.test.sovryn.app/data/rates-history
+
REACT_APP_GRAPH_RSK=https://subgraph.test.sovryn.app/subgraphs/name/DistributedCollective/sovryn-subgraph
REACT_APP_GRAPH_BOB=https://bob-ambient-subgraph.test.sovryn.app/subgraphs/name/DistributedCollective/bob-ambient-subgraph
@@ -23,4 +25,4 @@ REACT_APP_ENABLE_SERVICE_WORKER=false
REACT_APP_SIMULATE_TX=false
REACT_APP_ESTIMATOR_URI=https://simulator.sovryn.app
-REACT_APP_DATADOG_CLIENT_TOKEN=
\ No newline at end of file
+REACT_APP_DATADOG_CLIENT_TOKEN=
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/AaveReserveOverviewPage.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/AaveReserveOverviewPage.tsx
index 68857ca5c..eea55516d 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/AaveReserveOverviewPage.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/AaveReserveOverviewPage.tsx
@@ -9,8 +9,8 @@ import { Paragraph, Tabs, TabSize, TabType } from '@sovryn/ui';
import { useAaveReservesData } from '../../../hooks/aave/useAaveReservesData';
import { translations } from '../../../locales/i18n';
-import { TAB_ITEMS } from './AaveReserveOverviewPage.constants';
import { COMMON_SYMBOLS } from '../../../utils/asset';
+import { TAB_ITEMS } from './AaveReserveOverviewPage.constants';
import { BorrowDetailsGraph } from './components/BorrowDetailsGraph/BorrowDetailsGraph';
import { EModeDetails } from './components/EModeDetails/EModeDetails';
import { InterestRateModelGraph } from './components/InterestRateModelGraph/InterestRateModelGraph';
@@ -84,7 +84,7 @@ const AaveReserveOverviewPage: FC = () => {
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/BorrowDetailsGraph.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/BorrowDetailsGraph.tsx
index ae6ce89e1..68e9fdb7f 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/BorrowDetailsGraph.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/BorrowDetailsGraph.tsx
@@ -7,7 +7,13 @@ import { Accordion, Link, Paragraph } from '@sovryn/ui';
import { AmountRenderer } from '../../../../2_molecules/AmountRenderer/AmountRenderer';
import { StatisticsCard } from '../../../../2_molecules/StatisticsCard/StatisticsCard';
+import { AAVE_CONTRACT_ADDRESSES } from '../../../../../constants/aave';
import { Reserve } from '../../../../../hooks/aave/useAaveReservesData';
+import {
+ useAaveReservesHistory,
+ ReserveRateTimeRange,
+ ESupportedTimeRanges,
+} from '../../../../../hooks/aave/useAaveReservesHistory';
import { useIsMobile } from '../../../../../hooks/useIsMobile';
import { translations } from '../../../../../locales/i18n';
import { formatAmountWithSuffix } from '../../../../../utils/math';
@@ -28,6 +34,23 @@ export const BorrowDetailsGraph: FC
= ({
const borrowStats = useMemo(() => normalizeBorrowStats(reserve), [reserve]);
+ const [timeRange, setTimeRange] = useState(
+ ESupportedTimeRanges.OneMonth,
+ );
+ const { data: history } = useAaveReservesHistory(
+ `${reserve.underlyingAsset}${AAVE_CONTRACT_ADDRESSES.POOL_ADDRESSES_PROVIDER}`,
+ timeRange,
+ );
+
+ const borrowChartData = useMemo(
+ () =>
+ history.map(i => ({
+ x: i.date,
+ y: i.variableBorrowRate * 100,
+ })),
+ [history],
+ );
+
return (
= ({
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/components/Chart/Chart.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/components/Chart/Chart.tsx
index 77febba4a..9f93e5f24 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/components/Chart/Chart.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/BorrowDetailsGraph/components/Chart/Chart.tsx
@@ -3,6 +3,8 @@ import React, { FC, useCallback, useEffect, useRef } from 'react';
import ChartLibrary from 'chart.js/auto';
import 'chartjs-adapter-date-fns';
+import { TimeRangeButtons } from '../../../TimeRangeButtons/TimeRangeButtons';
+import { ReserveRateTimeRange } from './../../../../../../../hooks/aave/useAaveReservesHistory';
import {
CUSTOM_CANVAS_BACKGROUND_COLOR,
GRID_COLOR,
@@ -13,9 +15,10 @@ import { htmlLegendPlugin } from './Chart.utils';
type ChartProps = {
input: ChartData;
+ onTimeRangeChange: (range: ReserveRateTimeRange) => void;
};
-export const Chart: FC
= ({ input }) => {
+export const Chart: FC = ({ input, onTimeRangeChange }) => {
const canvas = useRef(null);
const chartRef = useRef(null);
@@ -111,7 +114,10 @@ export const Chart: FC = ({ input }) => {
return (
);
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/InterestRateModelGraph.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/InterestRateModelGraph.tsx
index 9bd05ed4d..98e9dd2ee 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/InterestRateModelGraph.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/InterestRateModelGraph.tsx
@@ -1,6 +1,7 @@
import React, { FC, useMemo, useState } from 'react';
import { t } from 'i18next';
+import { useSearchParams } from 'react-router-dom';
import { theme } from '@sovryn/tailwindcss-config';
import { Accordion, Link } from '@sovryn/ui';
@@ -9,12 +10,13 @@ import { Decimal } from '@sovryn/utils';
import { AmountRenderer } from '../../../../2_molecules/AmountRenderer/AmountRenderer';
import { StatisticsCard } from '../../../../2_molecules/StatisticsCard/StatisticsCard';
import { AAVE_CONTRACT_ADDRESSES } from '../../../../../constants/aave';
+import { useAaveInterestRatesData } from '../../../../../hooks/aave/useAaveRates';
import { Reserve } from '../../../../../hooks/aave/useAaveReservesData';
import { useIsMobile } from '../../../../../hooks/useIsMobile';
import { translations } from '../../../../../locales/i18n';
import { getBobExplorerUrl } from '../../../../../utils/helpers';
import { Chart } from './components/Chart/Chart';
-import { RatesData } from './components/Chart/Chart.types';
+import { COMMON_SYMBOLS } from '../../../../../utils/asset';
const pageTranslations = translations.aaveReserveOverviewPage.interestRateModel;
@@ -25,6 +27,9 @@ type InterestRateModelGraphProps = {
export const InterestRateModelGraph: FC = ({
reserve,
}) => {
+ const [searchParams] = useSearchParams();
+ const symbol = searchParams.get('asset') || COMMON_SYMBOLS.ETH;
+ const { data: rates } = useAaveInterestRatesData(symbol);
const { isMobile } = useIsMobile();
const [open, setOpen] = useState(true);
@@ -46,6 +51,9 @@ export const InterestRateModelGraph: FC = ({
[reserve.borrowUsageRatio],
);
+ if (!rates) {
+ return null;
+ }
return (
= ({
{/* statistics */}
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.tsx
index 8ef847dc6..36b05f3a5 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.tsx
@@ -33,7 +33,7 @@ export const Chart: FC = ({ meta, rates }) => {
() =>
CHART_PERCENTAGES.map(x => ({
x: x * 100,
- y: calculateVariableInterestRateModel(x, rates) * 100,
+ y: calculateVariableInterestRateModel(x, rates).mul(100).toNumber(),
})),
[rates],
);
@@ -41,7 +41,7 @@ export const Chart: FC = ({ meta, rates }) => {
const optimalPercentage = useMemo(
() =>
rates.optimalUsageRatio
- ? parseFloat((parseFloat(rates.optimalUsageRatio) * 100).toFixed(2))
+ ? Math.round(rates.optimalUsageRatio.mul(100).toNumber() * 100) / 100
: 0,
[rates.optimalUsageRatio],
);
@@ -49,7 +49,7 @@ export const Chart: FC = ({ meta, rates }) => {
const currentPercentage = useMemo(
() =>
rates.currentUsageRatio
- ? parseFloat((parseFloat(rates.currentUsageRatio) * 100).toFixed(2))
+ ? Math.round(rates.currentUsageRatio.mul(100).toNumber() * 100) / 100
: 0,
[rates.currentUsageRatio],
);
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.types.ts b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.types.ts
index 8a45374fa..11a089eb5 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.types.ts
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.types.ts
@@ -1,3 +1,5 @@
+import { Decimal } from '@sovryn/utils';
+
export type MockData = {
data1: T[];
data2: T[];
@@ -8,13 +10,13 @@ export type MockData = {
};
export interface RatesData {
- currentUsageRatio: string;
- optimalUsageRatio: string;
- baseVariableBorrowRate: string;
- variableRateSlope1: string;
- variableRateSlope2: string;
+ currentUsageRatio: Decimal;
+ optimalUsageRatio: Decimal;
+ baseVariableBorrowRate: Decimal;
+ variableRateSlope1: Decimal;
+ variableRateSlope2: Decimal;
underlyingAsset: string;
name: string;
symbol: string;
- decimals: string;
+ decimals: number;
}
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.utils.ts b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.utils.ts
index 2588ef4e6..463bfc902 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.utils.ts
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/InterestRateModelGraph/components/Chart/Chart.utils.ts
@@ -1,6 +1,8 @@
+import { Decimal } from '@sovryn/utils';
+
import { RatesData } from './Chart.types';
-const getOrCreateLegendList = id => {
+const getOrCreateLegendList = (id: string) => {
const legendContainer = document.getElementById(id);
if (!legendContainer) {
return;
@@ -92,35 +94,41 @@ export const htmlLegendPlugin = {
export const calculateInterestRateModel = (
utilization: number,
- baseRate: number,
- optimalUtilization: number,
- initialSlope: number,
- secondarySlope: number,
-): number => {
+ baseRate: Decimal,
+ optimalUtilization: Decimal,
+ initialSlope: Decimal,
+ secondarySlope: Decimal,
+): Decimal => {
if (utilization === 0) {
- return 0;
+ return Decimal.ZERO;
}
- if (utilization <= optimalUtilization) {
- return baseRate + (utilization / optimalUtilization) * initialSlope;
+ const utilizationDecimal = Decimal.from(utilization);
+
+ if (utilizationDecimal.lte(optimalUtilization)) {
+ return baseRate.add(
+ utilizationDecimal.div(optimalUtilization).mul(initialSlope),
+ );
}
- return (
- baseRate +
- initialSlope +
- ((utilization - optimalUtilization) / (1 - optimalUtilization)) *
- secondarySlope
- );
+ return baseRate
+ .add(initialSlope)
+ .add(
+ utilizationDecimal
+ .sub(optimalUtilization)
+ .div(Decimal.from(1).sub(optimalUtilization))
+ .mul(secondarySlope),
+ );
};
export const calculateVariableInterestRateModel = (
utilization: number,
rates: RatesData,
-): number => {
- const baseRate = parseFloat(rates.baseVariableBorrowRate);
- const optimalUtilization = parseFloat(rates.optimalUsageRatio);
- const initialSlope = parseFloat(rates.variableRateSlope1);
- const secondarySlope = parseFloat(rates.variableRateSlope2);
+) => {
+ const baseRate = rates.baseVariableBorrowRate;
+ const optimalUtilization = rates.optimalUsageRatio;
+ const initialSlope = rates.variableRateSlope1;
+ const secondarySlope = rates.variableRateSlope2;
return calculateInterestRateModel(
utilization,
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/SupplyDetailsGraph.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/SupplyDetailsGraph.tsx
index 81e49fece..1f24b7766 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/SupplyDetailsGraph.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/SupplyDetailsGraph.tsx
@@ -8,7 +8,13 @@ import { Accordion, Icon, IconNames, Paragraph } from '@sovryn/ui';
import { AmountRenderer } from '../../../../2_molecules/AmountRenderer/AmountRenderer';
import { StatisticsCard } from '../../../../2_molecules/StatisticsCard/StatisticsCard';
+import { AAVE_CONTRACT_ADDRESSES } from '../../../../../constants/aave';
import { Reserve } from '../../../../../hooks/aave/useAaveReservesData';
+import {
+ ESupportedTimeRanges,
+ ReserveRateTimeRange,
+ useAaveReservesHistory,
+} from '../../../../../hooks/aave/useAaveReservesHistory';
import { useIsMobile } from '../../../../../hooks/useIsMobile';
import { translations } from '../../../../../locales/i18n';
import { formatAmountWithSuffix } from '../../../../../utils/math';
@@ -29,6 +35,23 @@ export const SupplyDetailsGraph: FC = ({
const supplyStats = useMemo(() => normalizeSupplyStats(reserve), [reserve]);
+ const [timeRange, setTimeRange] = useState(
+ ESupportedTimeRanges.OneMonth,
+ );
+ const { data: history } = useAaveReservesHistory(
+ `${reserve.underlyingAsset}${AAVE_CONTRACT_ADDRESSES.POOL_ADDRESSES_PROVIDER}`,
+ timeRange,
+ );
+
+ const supplyChartData = useMemo(
+ () =>
+ history.map(i => ({
+ x: i.date,
+ y: i.liquidityRate * 100,
+ })),
+ [history],
+ );
+
return (
= ({
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/components/Chart/Chart.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/components/Chart/Chart.tsx
index 95525e619..10b4d6097 100644
--- a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/components/Chart/Chart.tsx
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/SupplyDetailsGraph/components/Chart/Chart.tsx
@@ -3,6 +3,8 @@ import React, { FC, useCallback, useEffect, useRef } from 'react';
import ChartLibrary from 'chart.js/auto';
import 'chartjs-adapter-date-fns';
+import { ReserveRateTimeRange } from '../../../../../../../hooks/aave/useAaveReservesHistory';
+import { TimeRangeButtons } from '../../../TimeRangeButtons/TimeRangeButtons';
import {
CUSTOM_CANVAS_BACKGROUND_COLOR,
GRID_COLOR,
@@ -13,9 +15,10 @@ import { htmlLegendPlugin } from './Chart.utils';
type ChartProps = {
input: InputData<{ x: number; y: number }>;
+ onTimeRangeChange: (range: ReserveRateTimeRange) => void;
};
-export const Chart: FC
= ({ input }) => {
+export const Chart: FC = ({ input, onTimeRangeChange }) => {
const canvas = useRef(null);
const chartRef = useRef(null);
@@ -113,7 +116,10 @@ export const Chart: FC = ({ input }) => {
return (
);
diff --git a/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/TimeRangeButtons/TimeRangeButtons.tsx b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/TimeRangeButtons/TimeRangeButtons.tsx
new file mode 100644
index 000000000..7c634cce2
--- /dev/null
+++ b/apps/frontend/src/app/5_pages/AaveReserveOverviewPage/components/TimeRangeButtons/TimeRangeButtons.tsx
@@ -0,0 +1,60 @@
+import React, { FC, useCallback, useState } from 'react';
+
+import {
+ ESupportedTimeRanges,
+ ReserveRateTimeRange,
+} from '../../../../../hooks/aave/useAaveReservesHistory';
+
+interface TimeRangeButtonsProps {
+ onChange: (range: ReserveRateTimeRange) => void;
+}
+
+export const TimeRangeButtons: FC = ({ onChange }) => {
+ const [activeRange, setActiveRange] = useState(
+ ESupportedTimeRanges.OneMonth,
+ );
+
+ const handleClick = useCallback(
+ (range: ReserveRateTimeRange) => {
+ setActiveRange(range);
+ onChange(range);
+ },
+ [onChange],
+ );
+
+ return (
+
+ {' '}
+
+
+
+
+ );
+};
diff --git a/apps/frontend/src/constants/aave.ts b/apps/frontend/src/constants/aave.ts
index 9c5fa4dfb..fe83dccc3 100644
--- a/apps/frontend/src/constants/aave.ts
+++ b/apps/frontend/src/constants/aave.ts
@@ -1,4 +1,5 @@
import { IS_MAINNET } from '../config/chains';
+
import { decimalic } from '../utils/math';
export const EMODE_DISABLED_ID = 0;
@@ -25,4 +26,5 @@ export const AAVE_CONTRACT_ADDRESSES = IS_MAINNET
WETH: '0x8CEc2719a2e896A11eA3f10406EfDb6Ad87281D9',
TREASURY: '0x2a9d8f5b2f7b8f5b4b5d5e7f3b4b5d5b5d5d5d5d',
INTEREST_RATE_STRATEGY: '0x847A3364Cc5fE389283bD821cfC8A477288D9e82',
+ RATES_HISTORY_API_URL: process.env.REACT_APP_RATES_HISTORY_API_URL,
};
diff --git a/apps/frontend/src/hooks/aave/useAaveRates.tsx b/apps/frontend/src/hooks/aave/useAaveRates.tsx
new file mode 100644
index 000000000..24bf33a58
--- /dev/null
+++ b/apps/frontend/src/hooks/aave/useAaveRates.tsx
@@ -0,0 +1,70 @@
+import { useMemo, useState } from 'react';
+
+import { formatUnits } from 'ethers/lib/utils';
+
+import { Decimal } from '@sovryn/utils';
+
+import { AaveCalculations } from '../../utils/aave/AaveCalculations';
+import { RAY_DECIMALS } from '../../utils/math';
+import { useAaveReservesData } from './useAaveReservesData';
+
+export interface RatesDataResult {
+ currentUsageRatio: Decimal;
+ optimalUsageRatio: Decimal;
+ baseVariableBorrowRate: Decimal;
+ variableRateSlope1: Decimal;
+ variableRateSlope2: Decimal;
+ underlyingAsset: string;
+ name: string;
+ symbol: string;
+ decimals: number;
+}
+
+export const useAaveInterestRatesData = (
+ symbol: string,
+): {
+ data: RatesDataResult | null;
+ error: string | null;
+} => {
+ const [data, setData] = useState(null);
+ const [error, setError] = useState(null);
+ const { reserves } = useAaveReservesData();
+ useMemo(() => {
+ const reserve = reserves.find(
+ r => r.symbol.toLocaleLowerCase() === symbol.toLocaleLowerCase(),
+ );
+ if (!reserve) {
+ return;
+ }
+ try {
+ const utilizationRate = AaveCalculations.calculateUtilizationRate(
+ reserve.decimals,
+ reserve.totalDebt,
+ reserve.availableLiquidity,
+ );
+ setData({
+ currentUsageRatio: utilizationRate,
+ optimalUsageRatio: Decimal.from(
+ formatUnits(reserve.optimalUsageRatio, RAY_DECIMALS),
+ ),
+ baseVariableBorrowRate: Decimal.from(
+ formatUnits(reserve.baseVariableBorrowRate, RAY_DECIMALS),
+ ),
+ variableRateSlope1: Decimal.from(
+ formatUnits(reserve.variableRateSlope1, RAY_DECIMALS),
+ ),
+ variableRateSlope2: Decimal.from(
+ formatUnits(reserve.variableRateSlope2, RAY_DECIMALS),
+ ),
+ underlyingAsset: reserve.underlyingAsset,
+ name: reserve.name,
+ symbol: reserve.symbol,
+ decimals: reserve.decimals,
+ });
+ } catch (error) {
+ setError(error.message);
+ }
+ }, [reserves, symbol]);
+
+ return { data, error };
+};
diff --git a/apps/frontend/src/hooks/aave/useAaveReservesHistory.tsx b/apps/frontend/src/hooks/aave/useAaveReservesHistory.tsx
new file mode 100644
index 000000000..790dbb950
--- /dev/null
+++ b/apps/frontend/src/hooks/aave/useAaveReservesHistory.tsx
@@ -0,0 +1,152 @@
+import { useCallback, useEffect, useState } from 'react';
+
+import axios from 'axios';
+import dayjs from 'dayjs';
+
+import { AAVE_CONTRACT_ADDRESSES } from '../../constants/aave';
+
+export enum ESupportedTimeRanges {
+ OneMonth = '1m',
+ ThreeMonths = '3m',
+ SixMonths = '6m',
+ OneYear = '1y',
+ TwoYears = '2y',
+ FiveYears = '5y',
+}
+
+export const reserveRateTimeRangeOptions = [
+ ESupportedTimeRanges.OneMonth,
+ ESupportedTimeRanges.SixMonths,
+ ESupportedTimeRanges.OneYear,
+];
+export type ReserveRateTimeRange = typeof reserveRateTimeRangeOptions[number];
+
+type RatesHistoryParams = {
+ from: number;
+ resolutionInHours: number;
+};
+
+type APIResponse = {
+ liquidityRate_avg: number;
+ variableBorrowRate_avg: number;
+ stableBorrowRate_avg: number;
+ utilizationRate_avg: number;
+ x: { year: number; month: number; date: number; hours: number };
+};
+
+const requestCache = new Map>();
+const fetchStats = async (
+ reserveId: string,
+ timeRange: ReserveRateTimeRange,
+ endpointURL: string,
+): Promise => {
+ const { from, resolutionInHours } = resolutionForTimeRange(timeRange);
+ const qs = `reserveId=${reserveId}&from=${from}&resolutionInHours=${resolutionInHours}`;
+ const url = `${endpointURL}?${qs}`;
+
+ if (requestCache.has(url)) {
+ return requestCache.get(url)!;
+ }
+
+ const requestPromise = axios
+ .get(url)
+ .then(response => response.data)
+ .finally(() => {
+ requestCache.delete(url);
+ });
+
+ requestCache.set(url, requestPromise);
+
+ return requestPromise;
+};
+
+const resolutionForTimeRange = (
+ timeRange: ReserveRateTimeRange,
+): RatesHistoryParams => {
+ switch (timeRange) {
+ case ESupportedTimeRanges.OneMonth:
+ return {
+ from: dayjs().subtract(30, 'day').unix(),
+ resolutionInHours: 6,
+ };
+ case ESupportedTimeRanges.SixMonths:
+ return {
+ from: dayjs().subtract(6, 'month').unix(),
+ resolutionInHours: 24,
+ };
+ case ESupportedTimeRanges.OneYear:
+ return {
+ from: dayjs().subtract(1, 'year').unix(),
+ resolutionInHours: 24,
+ };
+ default:
+ return {
+ // Return today as a fallback
+ from: dayjs().unix(),
+ resolutionInHours: 6,
+ };
+ }
+};
+
+export type FormattedReserveHistoryItem = {
+ date: number;
+ liquidityRate: number;
+ variableBorrowRate: number;
+};
+
+export function useAaveReservesHistory(
+ reserveId: string,
+ timeRange: ReserveRateTimeRange,
+) {
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(false);
+ const [data, setData] = useState([]);
+
+ const refetchData = useCallback(() => {
+ if (reserveId && AAVE_CONTRACT_ADDRESSES.RATES_HISTORY_API_URL) {
+ // reset
+ setLoading(true);
+ setError(false);
+ setData([]);
+ fetchStats(
+ reserveId,
+ timeRange,
+ AAVE_CONTRACT_ADDRESSES.RATES_HISTORY_API_URL,
+ )
+ .then((data: APIResponse[]) => {
+ setData(
+ data.map(d => ({
+ date: dayjs()
+ .set('year', d.x.year)
+ .set('month', d.x.month)
+ .set('date', d.x.date)
+ .set('hour', d.x.hours)
+ .valueOf(),
+ liquidityRate: d.liquidityRate_avg,
+ variableBorrowRate: d.variableBorrowRate_avg,
+ })),
+ );
+ })
+ .catch(e => {
+ console.error(
+ 'useReservesHistory(): Failed to fetch historical reserve data.',
+ e,
+ );
+ setError(true);
+ })
+ .finally(() => setLoading(false));
+ }
+
+ return () => null;
+ }, [reserveId, timeRange]);
+
+ useEffect(() => {
+ refetchData();
+ }, [refetchData]);
+ return {
+ loading,
+ data,
+ error,
+ refetch: refetchData,
+ };
+}
diff --git a/apps/frontend/src/locales/en/translations.json b/apps/frontend/src/locales/en/translations.json
index d175ee305..94f1ff7cb 100644
--- a/apps/frontend/src/locales/en/translations.json
+++ b/apps/frontend/src/locales/en/translations.json
@@ -999,7 +999,7 @@
"of": "of",
"apy": "APY",
"chart": {
- "label1": "Supply APR"
+ "suppApr": "Supply APR"
}
},
"borrowDetails": {
@@ -1015,7 +1015,7 @@
"collectorContract": "Collector contract",
"viewContract": "View contract",
"chart": {
- "label1": "Borrow APR, variable"
+ "aprVarLabel": "Borrow APR, variable"
}
},
"interestRateModel": {
@@ -1027,7 +1027,7 @@
"collectorContract": "Collector contract",
"viewContract": "View contract",
"chart": {
- "label1": "Borrow APR, variable"
+ "aprVarLabel": "Borrow APR, variable"
}
}
},
diff --git a/apps/frontend/src/utils/aave/AaveCalculations.ts b/apps/frontend/src/utils/aave/AaveCalculations.ts
index 910b964b5..00246dabd 100644
--- a/apps/frontend/src/utils/aave/AaveCalculations.ts
+++ b/apps/frontend/src/utils/aave/AaveCalculations.ts
@@ -1,3 +1,5 @@
+import { utils } from 'ethers';
+
import { Decimal } from '@sovryn/utils';
import { UserSummary } from './AaveUserReservesSummary';
@@ -128,4 +130,41 @@ export class AaveCalculations {
}
return borrowSize.mul(currentLiquidationThreshold).div(collateralBalance);
}
+
+ static calculateUtilizationRate = (
+ decimals: number,
+ totalDebt: string,
+ availableLiquidity: string,
+ ): Decimal => {
+ const totalBorrowBigInt = BigInt(
+ utils.parseUnits(totalDebt, decimals).toString(),
+ );
+ const availableLiquidityBigInt = BigInt(availableLiquidity);
+
+ const totalSupplyBigInt = totalBorrowBigInt + availableLiquidityBigInt;
+
+ if (totalSupplyBigInt === BigInt(0)) {
+ return Decimal.from(0);
+ }
+
+ const utilizationRateBigInt =
+ (totalBorrowBigInt * BigInt(10 ** decimals)) / totalSupplyBigInt;
+
+ const utilizationRate = utils.formatUnits(
+ utilizationRateBigInt.toString(),
+ decimals,
+ );
+
+ const utilizationRateDecimal = Decimal.from(utilizationRate);
+
+ const isBetweenZeroAndOne =
+ utilizationRateDecimal.gte(Decimal.from(0)) &&
+ utilizationRateDecimal.lte(Decimal.from(1));
+
+ if (!isBetweenZeroAndOne) {
+ return Decimal.from(0);
+ }
+
+ return utilizationRateDecimal;
+ };
}
diff --git a/apps/frontend/src/utils/math.ts b/apps/frontend/src/utils/math.ts
index 68b86bf6c..cd4b0d1a3 100644
--- a/apps/frontend/src/utils/math.ts
+++ b/apps/frontend/src/utils/math.ts
@@ -11,6 +11,8 @@ const DEFAULT_DECIMALS = 6;
const DEFAULT_DECIMALS_SEPARATOR = '.';
const DEFAULT_THOUSANDS_SEPARATOR = ',';
+export const RAY_DECIMALS = 27;
+
const unitNames = ['wei', 'kwei', 'mwei', 'gwei', 'szabo', 'finney', 'ether'];
// helper function to convert any type of ethers value to wei.