Skip to content

Commit

Permalink
Merge pull request #63 from agoric-labs/rs-address-decimal-places
Browse files Browse the repository at this point in the history
chore: use decimal places value in vault dashboard data
  • Loading branch information
rabi-siddique authored Jun 5, 2024
2 parents d5e2a3e + 0b45713 commit b1f377b
Show file tree
Hide file tree
Showing 15 changed files with 301 additions and 165 deletions.
30 changes: 21 additions & 9 deletions src/components/VaultCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,43 @@ import useSWR from 'swr/immutable';
import { VaultTotalLockedCollateralChart } from '@/widgets/VaultTotalLockedCollateralChart';
import { VaultTotalMintedISTChart } from '@/widgets/VaultTotalMintedISTChart';
import { VAULTS_DAILY_METRICS_QUERY } from '@/queries';
import { extractDailyOracles, populateMissingDays, subQueryFetcher } from '@/utils';
import { createNumberWithLeadingZeroes, extractDailyOracles, populateMissingDays, subQueryFetcher } from '@/utils';
import ChartsSkeleton from './ChartsSkeleton';
import { GRAPH_DAYS } from '@/constants';
import { DailyOracles, FormattedGraphData } from '@/types/common';
import { GraphData } from '@/types/psm-types';
import { VaultDailyMetricsQueryResponse } from '@/types/vault-types';
import { BoardAuxesMap, VaultDailyMetricsQueryResponse, VaultManagerMetricsNode } from '@/types/vault-types';

type Props = {
tokenNames: Array<string>;
vaultsDataIsLoading: boolean;
boardAuxes: BoardAuxesMap;
error: any;
};

export function populateGraphData(dailyOracles: DailyOracles, nodes: any[], graphData: Record<string, GraphData>): void {
export function populateGraphData(
dailyOracles: DailyOracles,
nodes: any,
boardAuxes: BoardAuxesMap,
graphData: Record<string, GraphData>,
): void {
for (let j = 0; j < nodes?.length; j++) {
const dailyTokenMetrics = nodes[j];
const dateKey = dailyTokenMetrics?.dateKey;

const oracle = (dailyOracles && dailyOracles[dateKey]) || { typeOutAmountLast: 1, typeInAmountLast: 1 };
const blockTime = dailyTokenMetrics?.blockTimeLast?.slice(0, 10);
const liquidatingCollateralBrand = dailyTokenMetrics?.liquidatingCollateralBrand;
const totalCollateralLast = dailyTokenMetrics?.totalCollateralLast;
const totalDebtLast = dailyTokenMetrics?.totalDebtLast / 1_000_000;
const decimalPlaces = (boardAuxes && boardAuxes[liquidatingCollateralBrand]) || 6;
const decimalPlacesIST = (boardAuxes && boardAuxes['IST']) || 6;
const divisor = createNumberWithLeadingZeroes(decimalPlaces);
const divisorIST = createNumberWithLeadingZeroes(decimalPlacesIST);

const totalCollateralLast = Number(dailyTokenMetrics?.totalCollateralLast);
const totalDebtLast = Number(dailyTokenMetrics?.totalDebtLast) / divisorIST;
const typeOutAmountLast = Number(oracle.typeOutAmountLast);
const typeInAmountLast = Number(oracle.typeInAmountLast);
const totalCollateral = (totalCollateralLast / 1_000_000) * (typeOutAmountLast / typeInAmountLast);
const totalCollateral = (totalCollateralLast / divisor) * (typeOutAmountLast / typeInAmountLast);

graphData[dateKey] = {
...graphData[dateKey],
Expand All @@ -43,6 +54,7 @@ export function populateGraphData(dailyOracles: DailyOracles, nodes: any[], grap
export function constructGraph(
tokenNames: string[],
dailyMetricsResponse: VaultDailyMetricsQueryResponse,
boardAuxes: BoardAuxesMap,
graphData: Record<string, GraphData>,
): FormattedGraphData[] {
for (let i = 0; i < tokenNames?.length; i++) {
Expand All @@ -51,15 +63,15 @@ export function constructGraph(

const nodes = dailyMetricsResponse?.[tokenName]?.nodes;
if (nodes) {
populateGraphData(dailyOracles, nodes, graphData);
populateGraphData(dailyOracles, nodes, boardAuxes, graphData);
}
}

const graphDataList = populateMissingDays(graphData, GRAPH_DAYS);
return graphDataList;
}

export function VaultCharts({ tokenNames, vaultsDataIsLoading, error }: Props) {
export function VaultCharts({ tokenNames, vaultsDataIsLoading, error, boardAuxes }: Props) {
const {
data: dailyMetricsData,
isLoading: graphDataIsLoading,
Expand All @@ -78,7 +90,7 @@ export function VaultCharts({ tokenNames, vaultsDataIsLoading, error }: Props) {
const dailyMetricsResponse: VaultDailyMetricsQueryResponse = dailyMetricsData?.data?.data;

const graphData: Record<string, GraphData> = {};
const graphDataList = constructGraph(tokenNames, dailyMetricsResponse, graphData);
const graphDataList = constructGraph(tokenNames, dailyMetricsResponse, boardAuxes, graphData);

return (
<>
Expand Down
60 changes: 46 additions & 14 deletions src/pages/Vaults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
OpenVaultsData,
VaultsDashboardResponse,
VaultsDashboardData,
BoardAuxesNode,
BoardAuxesMap,
} from '@/types/vault-types';

function processOraclePrices(nodes: OraclePriceNode[]): OraclePriceNodesData {
Expand All @@ -44,7 +46,7 @@ function processOraclePrices(nodes: OraclePriceNode[]): OraclePriceNodesData {
function processOracleDailyPrices(nodes: OraclePriceDailiesNode[]): OracleDailyPriceNodesData {
const obj: OracleDailyPriceNodesData = {};
return nodes?.reduce((agg, node) => {
const {typeInName} = node;
const { typeInName } = node;

if (!agg[typeInName]) {
agg[typeInName] = [];
Expand All @@ -68,10 +70,22 @@ function processManagerGovernancesNodes(nodes: VaultManagerGovernancesNode[]): V
}, obj);
}

function processBoardAuxes(nodes: BoardAuxesNode[]): BoardAuxesMap {
const obj: BoardAuxesMap = {};

for (let i = 0; i < nodes?.length; i++) {
const item = nodes[i];
obj[item.allegedName] = item.decimalPlaces;
}

return obj;
}

function processOpenVaultsData(
nodes: VaultsNode[],
oraclePrices: OraclePriceNodesData,
managerGovernancesNodes: VaultManagerGovernancesNodesData,
boardAuxes: BoardAuxesMap,
): OpenVaultsData {
const arr: OpenVaultsData = [];
const openVaultsData: OpenVaultsData = nodes?.reduce((acc, vaultNode) => {
Expand All @@ -80,11 +94,15 @@ function processOpenVaultsData(
throw new Error(`Node ID does not contain enough segments: ${vaultNode.id}`);
}
const managerName = idSegments.slice(0, 4).join('.');
const decimalPlaces = (boardAuxes && boardAuxes[vaultNode?.denom]) || 6;
const decimalPlacesIST = (boardAuxes && boardAuxes['IST']) || 6;

const combinedData = {
...oraclePrices[vaultNode.denom],
...managerGovernancesNodes[managerName],
...vaultNode,
decimalPlaces,
decimalPlacesIST,
};

acc.push(combinedData);
Expand All @@ -97,16 +115,19 @@ function processOpenVaultsData(

function processVaultsData(
vaultsDashboardResponse: VaultsDashboardResponse,
): [OpenVaultsData, VaultsDashboardData, string[]] {
): [OpenVaultsData, VaultsDashboardData, string[], BoardAuxesMap] {
const oraclePrices = processOraclePrices(vaultsDashboardResponse?.oraclePrices?.nodes);
const oracleDailyPrices = processOracleDailyPrices(vaultsDashboardResponse?.oraclePriceDailies?.nodes);
const managerGovernancesNodes = processManagerGovernancesNodes(
vaultsDashboardResponse?.vaultManagerGovernances?.nodes,
);
const boardAuxes = processBoardAuxes(vaultsDashboardResponse?.boardAuxes?.nodes);

const openVaults: OpenVaultsData = processOpenVaultsData(
vaultsDashboardResponse.vaults.nodes,
vaultsDashboardResponse?.vaults?.nodes,
oraclePrices,
managerGovernancesNodes,
boardAuxes,
);
const tokenNames: string[] =
vaultsDashboardResponse?.vaultManagerMetrics?.nodes?.map((node) => node.liquidatingCollateralBrand) || [];
Expand All @@ -118,18 +139,22 @@ function processVaultsData(
throw new Error(`Node ID does not contain enough segments: ${node.id}`);
}
const managerName = idSegments.slice(0, 4).join('.');
const { liquidatingCollateralBrand } = node;
const liquidatingCollateralBrand = node.liquidatingCollateralBrand;
const decimalPlaces = (boardAuxes && boardAuxes[liquidatingCollateralBrand]) || 6;
const decimalPlacesIST = (boardAuxes && boardAuxes['IST']) || 6;

agg[liquidatingCollateralBrand] = {
...managerGovernancesNodes[managerName],
...oraclePrices[liquidatingCollateralBrand],
...node,
oracleDailyPrices: [...(oracleDailyPrices[liquidatingCollateralBrand] || [])],
decimalPlaces,
decimalPlacesIST,
};
return agg;
}, dashboardData);

return [openVaults, dashboardData, tokenNames];
return [openVaults, dashboardData, tokenNames, boardAuxes];
}
export function Vaults() {
const {
Expand All @@ -140,12 +165,13 @@ export function Vaults() {

const vaultDataResponse: VaultsDashboardResponse = vaultsData?.data?.data;

const totalVaultsCount = vaultDataResponse?.vaults.totalCount || 1;
const totalVaultsCount = vaultDataResponse?.vaults?.totalCount || 1;
const pageCount = Math.ceil(totalVaultsCount / 100) - 1;
const { data: vaultsNextPages, error: nextPagesError, isLoading: nextPagesIsLoading } = useSWR<AxiosResponse, AxiosError>(
pageCount ? OPEN_VAULTS_NEXT_PAGES_QUERY(pageCount) : null,
subQueryFetcher,
);
const {
data: vaultsNextPages,
error: nextPagesError,
isLoading: nextPagesIsLoading,
} = useSWR<AxiosResponse, AxiosError>(pageCount ? OPEN_VAULTS_NEXT_PAGES_QUERY(pageCount) : null, subQueryFetcher);

let vaultsDataAppended: VaultsDashboardResponse | null = null;
if (!((pageCount !== 0 && !vaultsNextPages) || !vaultDataResponse)) {
Expand All @@ -154,11 +180,11 @@ export function Vaults() {
nodes: Array<VaultsNode>;
};
} = vaultsNextPages?.data?.data || {};
const nextVaults = Object.values(vaultPages).flatMap((openVaultsPage) => openVaultsPage.nodes);
const nextVaults = Object.values(vaultPages).flatMap((openVaultsPage) => openVaultsPage?.nodes);

vaultsDataAppended = {
...vaultDataResponse,
vaults: { ...vaultDataResponse.vaults, nodes: [...vaultDataResponse.vaults.nodes, ...nextVaults] },
vaults: { ...vaultDataResponse.vaults, nodes: [...vaultDataResponse?.vaults?.nodes, ...nextVaults] },
};
}

Expand All @@ -170,9 +196,10 @@ export function Vaults() {
let openVaults: OpenVaultsData = [];
let dashboardData: VaultsDashboardData = {};
let tokenNames: string[] = [];
let boardAuxes: BoardAuxesMap = {};

if (vaultsDataAppended && Object.keys(vaultsDataAppended).length > 0) {
[openVaults, dashboardData, tokenNames] = processVaultsData(vaultsDataAppended);
[openVaults, dashboardData, tokenNames, boardAuxes] = processVaultsData(vaultsDataAppended);
}
const dataIsLoading = isLoading || nextPagesIsLoading;

Expand All @@ -186,7 +213,12 @@ export function Vaults() {
<VaultTotalLockedCollateralValueCard data={dashboardData} isLoading={dataIsLoading} />
</ValueCardGrid>
<TokenPrices data={dashboardData} isLoading={dataIsLoading} />
<VaultCharts tokenNames={tokenNames} vaultsDataIsLoading={dataIsLoading} error={error} />
<VaultCharts
tokenNames={tokenNames}
boardAuxes={boardAuxes}
vaultsDataIsLoading={dataIsLoading}
error={error}
/>
<hr className="my-5" />
<VaultManagers data={dashboardData} isLoading={dataIsLoading} />
<hr className="my-5" />
Expand Down
10 changes: 8 additions & 2 deletions src/types/vault-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type OraclePriceDailiesNode = {
};

export type BoardAuxesNode = { allegedName: string; decimalPlaces: number };
export type BoardAuxesMap = { [allegedName: string]: number };

export type VaultsDashboardResponse = {
boardAuxes: { nodes: Array<BoardAuxesNode> };
Expand Down Expand Up @@ -81,7 +82,12 @@ export type OracleDailyPriceNodesData = { [key: string]: OraclePriceDailiesNode[
export type VaultManagerGovernancesNodesData = { [key: string]: VaultManagerGovernancesNode };

export type VaultsDashboardData = {
[key: string]: VaultManagerMetricsNode & OraclePriceNode & VaultManagerGovernancesNode & OraclePriceDailiesArr;
[key: string]: VaultManagerMetricsNode &
OraclePriceNode &
VaultManagerGovernancesNode &
OraclePriceDailiesArr & { decimalPlaces: number; decimalPlacesIST: number };
};

export type OpenVaultsData = Array<VaultsNode & OraclePriceNode & VaultManagerGovernancesNode>;
export type OpenVaultsData = Array<
VaultsNode & OraclePriceNode & VaultManagerGovernancesNode & { decimalPlaces: number; decimalPlacesIST: number }
>;
12 changes: 11 additions & 1 deletion src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,14 @@ export const extractDailyOracles = (tokenName: string, dailyMetricsResponse: any
}

return dailyOracles;
};
};

export function createNumberWithLeadingZeroes(numOfZeroes: number) {
if (numOfZeroes < 0) {
return NaN;
} else if (numOfZeroes === 0) {
return 1;
} else {
return Math.pow(10, numOfZeroes);
}
}
21 changes: 13 additions & 8 deletions src/widgets/OpenVaults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { OpenVaultsTable } from '@/components/OpenVaultsTable';
import { SectionHeader } from '@/components/SectionHeader';
import CollateralWithIcon from '@/components/ui/collateralWithIcon';
import { OpenVaultsData } from '@/types/vault-types';
import { createNumberWithLeadingZeroes } from '@/utils';

type Props = {
title?: string;
Expand Down Expand Up @@ -31,22 +32,26 @@ export function OpenVaults({ title = 'Open Vaults', data, isLoading }: Props) {

const rows = data.map((vaultData) => {
const vaultIdx = vaultData.id.split('.').at(-1)?.split('vault')[1] || '';
const typeOutAmount = Number(vaultData?.typeOutAmount) || 0
const collateralValueUsd = ((typeOutAmount / 1_000_000) * vaultData.balance) / 1_000_000;
const liquidationRatio = (vaultData?.liquidationMarginNumerator ?? 0) / (vaultData?.liquidationMarginDenominator ?? 1);
const istDebtAmount = vaultData.debt / 1_000_000;
const collateralAmount = vaultData.balance / 1_000_000;
const tokenDivisor = createNumberWithLeadingZeroes(vaultData?.decimalPlaces);
const tokenDivisorIST = createNumberWithLeadingZeroes(vaultData?.decimalPlacesIST);
const typeOutAmount = Number(vaultData?.typeOutAmount) || 0;
const typeInAmount = Number(vaultData?.typeInAmount) || 0;
const collateralAmount = vaultData.balance / tokenDivisor;
const collateralValueUsd = (typeOutAmount / typeInAmount) * collateralAmount;
const liquidationRatio =
(vaultData?.liquidationMarginNumerator ?? 0) / (vaultData?.liquidationMarginDenominator ?? 1);
const istDebtAmount = vaultData.debt / tokenDivisorIST;
const liquidationPrice = (istDebtAmount * liquidationRatio) / collateralAmount;
const currentCollateralPrice = typeOutAmount / 1_000_000;
const collateralizationRatio = collateralValueUsd / (vaultData.debt / 1_000_000);
const currentCollateralPrice = typeOutAmount / typeInAmount;
const collateralizationRatio = collateralValueUsd / istDebtAmount;

return {
vault_idx: vaultIdx,
collateral_type: <CollateralWithIcon collateralType={vaultData.denom} />,
debt_type: 'IST',
collateral_amount: collateralAmount,
current_collateral_price: currentCollateralPrice,
collateral_oracle_usd_value: typeOutAmount / 1_000_000,
collateral_oracle_usd_value: typeOutAmount / typeInAmount,
collateral_amount_current_usd: collateralValueUsd,
debt_amount: istDebtAmount,
liquidation_margin: liquidationRatio,
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/PSMMintedPoolBalancePie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function PSMMintedPoolBalancePie({ title = 'Total Minted IST Per Anchor',
label={RadianTooltip}
>
{pieChartData.map((item, idx) => (
<Cell fill={colors[idx % colors.length]} key={item.label} name={item.label} />
<Cell fill={colors[idx % colors.length]} key={idx} name={item.label} />
))}
</Pie>
<Tooltip />
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/ReserveHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function ReserveHistory({ title = 'History', data, tokenNames, isLoading,
<Tooltip />
<Legend />
{tokenNames.map((token, idx) => (
<Bar key={token} stackId="a" name={token} dataKey={token} fill={colors[idx % colors.length]} />
<Bar key={idx} stackId="a" name={token} dataKey={token} fill={colors[idx % colors.length]} />
))}
</BarChart>
</ResponsiveContainer>
Expand Down
25 changes: 17 additions & 8 deletions src/widgets/TokenPrices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SectionHeader } from '@/components/SectionHeader';
import { TokenPricesTable } from '@/components/TokenPricesTable';
import CollateralWithIcon from '@/components/ui/collateralWithIcon';
import { VaultsDashboardData } from '@/types/vault-types';
import { createNumberWithLeadingZeroes } from '@/utils';

type Props = {
title?: string;
Expand Down Expand Up @@ -30,15 +31,23 @@ export function TokenPrices({ title = 'Summary', data, isLoading }: Props) {
});

const oraclePrices = sortedEntries.map((token) => {
const typeOutAmount = Number(token?.typeOutAmount) || 0
const typeOutAmount = Number(token?.typeOutAmount) || 0;
const typeInAmount = Number(token?.typeInAmount) || 0;

const tokenDivisor = createNumberWithLeadingZeroes(token?.decimalPlaces);
// Determine 24h change in oracle price
const sortedOracleDailyPrices = token?.oracleDailyPrices?.sort((a, b) => b.dateKey - a.dateKey);
const changeValue = sortedOracleDailyPrices.length > 0 ? (() => {
const oraclePriceYesterday = sortedOracleDailyPrices[0].typeOutAmountLast / 1_000_000;
const oraclePriceToday = sortedOracleDailyPrices[1].typeOutAmountLast / 1_000_000;
const change = 1 - (oraclePriceToday / oraclePriceYesterday);
return Math.round(change * 10000) / 100
})() : null;
const changeValue =
sortedOracleDailyPrices.length > 0
? (() => {
const oraclePriceYesterday =
sortedOracleDailyPrices[0].typeOutAmountLast / sortedOracleDailyPrices[0].typeInAmountLast;
const oraclePriceToday =
sortedOracleDailyPrices[1].typeOutAmountLast / sortedOracleDailyPrices[1].typeInAmountLast;
const change = 1 - oraclePriceToday / oraclePriceYesterday;
return Math.round(change * 10000) / 100;
})()
: null;

let dayChange;
if (changeValue === null) {
Expand All @@ -53,7 +62,7 @@ export function TokenPrices({ title = 'Summary', data, isLoading }: Props) {
token: <CollateralWithIcon collateralType={token.liquidatingCollateralBrand} />,
name: token.liquidatingCollateralBrand,
numActive: token?.numActiveVaults || 0,
oraclePrice: typeOutAmount / 1_000_000,
oraclePrice: typeOutAmount / typeInAmount,
dayChange,
};
});
Expand Down
Loading

0 comments on commit b1f377b

Please sign in to comment.