diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 0d30170a..ceea24ae 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -54,7 +54,8 @@ export function MarketTableBody({ (columnVisibility.supplyAPY ? 1 : 0) + (columnVisibility.borrowAPY ? 1 : 0) + (columnVisibility.rateAtTarget ? 1 : 0) + - (columnVisibility.trustedBy ? 1 : 0); + (columnVisibility.trustedBy ? 1 : 0) + + (columnVisibility.utilizationRate ? 1 : 0); const getTrustedVaultsForMarket = (market: Market): TrustedVault[] => { if (!columnVisibility.trustedBy || !market.supplyingVaults?.length) { @@ -216,6 +217,13 @@ export function MarketTableBody({

)} + {columnVisibility.utilizationRate && ( + +

+ {`${(item.state.utilization * 100).toFixed(2)}%`} +

+ + )}
diff --git a/app/markets/components/columnVisibility.ts b/app/markets/components/columnVisibility.ts index eba18bd4..cdf57637 100644 --- a/app/markets/components/columnVisibility.ts +++ b/app/markets/components/columnVisibility.ts @@ -8,6 +8,7 @@ export type ColumnVisibility = { borrowAPY: boolean; rateAtTarget: boolean; trustedBy: boolean; + utilizationRate: boolean; }; export const DEFAULT_COLUMN_VISIBILITY: ColumnVisibility = { @@ -18,6 +19,7 @@ export const DEFAULT_COLUMN_VISIBILITY: ColumnVisibility = { borrowAPY: false, rateAtTarget: false, trustedBy: false, + utilizationRate: false, }; export const COLUMN_LABELS: Record = { @@ -28,6 +30,7 @@ export const COLUMN_LABELS: Record = { borrowAPY: 'Borrow APY', rateAtTarget: 'Target Rate', trustedBy: 'Trusted By', + utilizationRate: 'Utilization', }; export const COLUMN_DESCRIPTIONS: Record = { @@ -38,4 +41,5 @@ export const COLUMN_DESCRIPTIONS: Record = { borrowAPY: 'Annual percentage rate for borrowers', rateAtTarget: 'Interest rate at target utilization', trustedBy: 'Highlights your trusted vaults that currently supply this market', + utilizationRate: 'Percentage of supplied assets currently borrowed', }; diff --git a/app/markets/components/constants.ts b/app/markets/components/constants.ts index 0d446665..7b313484 100644 --- a/app/markets/components/constants.ts +++ b/app/markets/components/constants.ts @@ -10,6 +10,7 @@ export enum SortColumn { BorrowAPY = 9, RateAtTarget = 10, TrustedBy = 11, + UtilizationRate = 12, } // Gas cost to simplify tx flow: do not need to estimate gas for transactions diff --git a/app/markets/components/markets.tsx b/app/markets/components/markets.tsx index 24a0575e..0bfe2b33 100644 --- a/app/markets/components/markets.tsx +++ b/app/markets/components/markets.tsx @@ -341,6 +341,7 @@ export default function Markets({ [SortColumn.BorrowAPY]: 'state.borrowApy', [SortColumn.RateAtTarget]: 'state.apyAtTarget', [SortColumn.TrustedBy]: '', + [SortColumn.UtilizationRate]: 'state.utilization', }; const propertyPath = sortPropertyMap[sortColumn]; if (propertyPath) { diff --git a/app/markets/components/marketsTable.tsx b/app/markets/components/marketsTable.tsx index ec6d21fd..4c04ef29 100644 --- a/app/markets/components/marketsTable.tsx +++ b/app/markets/components/marketsTable.tsx @@ -174,6 +174,15 @@ function MarketsTable({ targetColumn={SortColumn.RateAtTarget} /> )} + {columnVisibility.utilizationRate && ( + + )} Risk Indicators Actions diff --git a/app/markets/components/utils.ts b/app/markets/components/utils.ts deleted file mode 100644 index 6786ba28..00000000 --- a/app/markets/components/utils.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ - -import { SupportedNetworks } from '@/utils/networks'; -import { parsePriceFeedVendors, PriceFeedVendors, getOracleType, OracleType } from '@/utils/oracle'; -import { ERC20Token } from '@/utils/tokens'; -import { Market } from '@/utils/types'; -import { SortColumn } from './constants'; - -export const sortProperties = { - [SortColumn.Starred]: 'uniqueKey', - [SortColumn.LoanAsset]: 'loanAsset.name', - [SortColumn.CollateralAsset]: 'collateralAsset.name', - [SortColumn.LLTV]: 'lltv', - [SortColumn.Supply]: 'state.supplyAssetsUsd', - [SortColumn.Borrow]: 'state.borrowAssetsUsd', - [SortColumn.SupplyAPY]: 'state.supplyApy', - [SortColumn.Liquidity]: 'state.liquidityAssets', - [SortColumn.BorrowAPY]: 'state.borrowApy', - [SortColumn.RateAtTarget]: 'state.apyAtTarget', - [SortColumn.TrustedBy]: 'uniqueKey', -}; - -export const getNestedProperty = (obj: Market, path: string | ((item: Market) => number)) => { - if (typeof path === 'function') { - return path(obj); - } - - if (!path) { - return undefined; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return path.split('.').reduce((acc, part) => acc && acc[part], obj as any); -}; - -const isSelectedAsset = ( - market: Market, - selectedAssetKeys: string[], - type: 'collateral' | 'loan', -) => { - return selectedAssetKeys.find((combinedKey) => - combinedKey - .split('|') - .includes( - `${(type === 'collateral' - ? market.collateralAsset - : market.loanAsset - ).address.toLowerCase()}-${market.morphoBlue.chain.id}`, - ), - ); -}; - -// Define the type for USD Filters -type UsdFilters = { - minSupply: string; - minBorrow: string; -}; - -export function applyFilterAndSort( - markets: Market[], - sortColumn: SortColumn, - sortDirection: number, - selectedNetwork: SupportedNetworks | null, - showUnknown: boolean, - showUnknownOracle: boolean, - selectedCollaterals: string[], - selectedLoanAssets: string[], - selectedOracles: PriceFeedVendors[], - staredIds: string[], - findToken: (address: string, chainId: number) => ERC20Token | undefined, - usdFilters: UsdFilters, - hideSmallMarkets?: boolean, -): Market[] { - const parseUsdValue = (value: string | null | undefined): number | null => { - if (value === null || value === undefined || value === '') return null; - const num = parseFloat(value); - return isNaN(num) ? null : num; - }; - - const minSupplyUsd = parseUsdValue(usdFilters.minSupply); - const minBorrowUsd = parseUsdValue(usdFilters.minBorrow); - - return markets - .filter((market) => { - if (selectedNetwork !== null && market.morphoBlue.chain.id !== selectedNetwork) { - return false; - } - - // todo: might need async function to search for tokens from API. - const collateralToken = findToken(market.collateralAsset.address, market.morphoBlue.chain.id); - const loanToken = findToken(market.loanAsset.address, market.morphoBlue.chain.id); - - if (!showUnknown && (!collateralToken || !loanToken)) { - return false; - } - - if (!showUnknownOracle) { - const info = market.oracle - ? parsePriceFeedVendors(market.oracle.data, market.morphoBlue.chain.id) - : null; - const isCustom = - getOracleType(market.oracle?.data, market.oracleAddress, market.morphoBlue.chain.id) === - OracleType.Custom; - const isUnknown = isCustom || (info?.hasUnknown ?? false); - if (!market.oracle || isUnknown) return false; - } - - if ( - (selectedCollaterals.length > 0 && - !isSelectedAsset(market, selectedCollaterals, 'collateral')) || - (selectedLoanAssets.length > 0 && !isSelectedAsset(market, selectedLoanAssets, 'loan')) - ) { - return false; - } - - if (selectedOracles.length > 0 && !!market.oracle) { - const marketOracles = parsePriceFeedVendors( - market.oracle.data, - market.morphoBlue.chain.id, - ).vendors; - if (!marketOracles.some((oracle) => selectedOracles.includes(oracle))) { - return false; - } - } - - // Add USD Filters - only apply if hideSmallMarkets is true (or undefined for backwards compatibility) - if (hideSmallMarkets !== false) { - const supplyUsd = parseUsdValue(market.state?.supplyAssetsUsd?.toString()); // Use optional chaining - const borrowUsd = parseUsdValue(market.state?.borrowAssetsUsd?.toString()); // Use optional chaining - - if (minSupplyUsd !== null && (supplyUsd === null || supplyUsd < minSupplyUsd)) { - return false; - } - if (minBorrowUsd !== null && (borrowUsd === null || borrowUsd < minBorrowUsd)) { - return false; - } - } - // End USD Filters - - return true; - }) - .sort((a, b) => { - let comparison = 0; - if (sortColumn === SortColumn.Starred) { - const aStared = staredIds.includes(a.uniqueKey); - const bStared = staredIds.includes(b.uniqueKey); - if (aStared && !bStared) return -1; - if (!aStared && bStared) return 1; - return 0; - } else { - const property = sortProperties[sortColumn]; - if (!property) { - return 0; - } - - const aValue = getNestedProperty(a, property); - const bValue = getNestedProperty(b, property); - comparison = aValue > bValue ? 1 : aValue < bValue ? -1 : 0; - } - return comparison * sortDirection; - }); -} diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx index d17bace3..38dcfa2b 100644 --- a/src/components/common/MarketsTableWithSameLoanAsset.tsx +++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx @@ -73,6 +73,7 @@ enum SortColumn { RateAtTarget = 6, Risk = 7, TrustedBy = 8, + UtilizationRate = 9, } function getTrustedVaultsForMarket( @@ -533,6 +534,13 @@ function MarketRow({

)} + {columnVisibility.utilizationRate && ( + +

+ {`${(market.state.utilization * 100).toFixed(2)}%`} +

+ + )} @@ -778,6 +786,7 @@ export function MarketsTableWithSameLoanAsset({ [SortColumn.RateAtTarget]: 'state.apyAtTarget', [SortColumn.Risk]: '', // No sorting for risk [SortColumn.TrustedBy]: '', + [SortColumn.UtilizationRate]: 'state.utilization', }; const propertyPath = sortPropertyMap[sortColumn]; @@ -826,7 +835,15 @@ export function MarketsTableWithSameLoanAsset({ const safePage = Math.min(Math.max(1, currentPage), totalPages); const startIndex = (safePage - 1) * safePerPage; const paginatedMarkets = processedMarkets.slice(startIndex, startIndex + safePerPage); - const emptyStateColumns = (showSelectColumn ? 7 : 6) + (columnVisibility.trustedBy ? 1 : 0); + const emptyStateColumns = (showSelectColumn ? 7 : 6) + + (columnVisibility.trustedBy ? 1 : 0) + + (columnVisibility.totalSupply ? 1 : 0) + + (columnVisibility.totalBorrow ? 1 : 0) + + (columnVisibility.liquidity ? 1 : 0) + + (columnVisibility.supplyAPY ? 1 : 0) + + (columnVisibility.borrowAPY ? 1 : 0) + + (columnVisibility.rateAtTarget ? 1 : 0) + + (columnVisibility.utilizationRate ? 1 : 0); React.useEffect(() => { setCurrentPage(1); @@ -1020,6 +1037,15 @@ export function MarketsTableWithSameLoanAsset({ onSort={handleSort} /> )} + {columnVisibility.utilizationRate && ( + + )} Indicators