From c98ae2971d7c413f9755d63286b927cbae076a90 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sun, 30 Nov 2025 11:48:13 +0800 Subject: [PATCH 1/4] feat: add utlization colume --- app/markets/components/MarketTableBody.tsx | 16 ++++++++- app/markets/components/columnVisibility.ts | 4 +++ app/markets/components/constants.ts | 1 + app/markets/components/markets.tsx | 1 + app/markets/components/marketsTable.tsx | 9 +++++ .../common/MarketsTableWithSameLoanAsset.tsx | 34 ++++++++++++++++++- 6 files changed, 63 insertions(+), 2 deletions(-) diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 0d30170a..2c1d291b 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,19 @@ export function MarketTableBody({

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

+ {(() => { + const supplyUsd = item.state.supplyAssetsUsd; + const borrowUsd = item.state.borrowAssetsUsd; + if (!supplyUsd || supplyUsd === 0) return '—'; + const utilization = (borrowUsd / supplyUsd) * 100; + return `${utilization.toFixed(2)}%`; + })()} +

+ + )}
diff --git a/app/markets/components/columnVisibility.ts b/app/markets/components/columnVisibility.ts index eba18bd4..72c12885 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 (borrow / supply)', }; 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/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx index d17bace3..1279b73e 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,19 @@ function MarketRow({

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

+ {(() => { + const supplyUsd = market.state.supplyAssetsUsd; + const borrowUsd = market.state.borrowAssetsUsd; + if (!supplyUsd || supplyUsd === 0) return '—'; + const utilization = (borrowUsd / supplyUsd) * 100; + return `${utilization.toFixed(2)}%`; + })()} +

+ + )} @@ -778,6 +792,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 +841,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 +1043,15 @@ export function MarketsTableWithSameLoanAsset({ onSort={handleSort} /> )} + {columnVisibility.utilizationRate && ( + + )} Indicators From fab5ed8acebfb5b79071a16fc7028b9f4fc52ec1 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sun, 30 Nov 2025 11:52:53 +0800 Subject: [PATCH 2/4] chore: remove unused file --- app/markets/components/columnVisibility.ts | 2 +- app/markets/components/utils.ts | 162 --------------------- 2 files changed, 1 insertion(+), 163 deletions(-) delete mode 100644 app/markets/components/utils.ts diff --git a/app/markets/components/columnVisibility.ts b/app/markets/components/columnVisibility.ts index 72c12885..cdf57637 100644 --- a/app/markets/components/columnVisibility.ts +++ b/app/markets/components/columnVisibility.ts @@ -41,5 +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 (borrow / supply)', + utilizationRate: 'Percentage of supplied assets currently borrowed', }; 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; - }); -} From b1be0f46b8a486f4f1c2ac2b7c390930739db8ec Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sun, 30 Nov 2025 11:57:56 +0800 Subject: [PATCH 3/4] chore: simplify display --- app/markets/components/MarketTableBody.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/markets/components/MarketTableBody.tsx b/app/markets/components/MarketTableBody.tsx index 2c1d291b..ceea24ae 100644 --- a/app/markets/components/MarketTableBody.tsx +++ b/app/markets/components/MarketTableBody.tsx @@ -220,13 +220,7 @@ export function MarketTableBody({ {columnVisibility.utilizationRate && (

- {(() => { - const supplyUsd = item.state.supplyAssetsUsd; - const borrowUsd = item.state.borrowAssetsUsd; - if (!supplyUsd || supplyUsd === 0) return '—'; - const utilization = (borrowUsd / supplyUsd) * 100; - return `${utilization.toFixed(2)}%`; - })()} + {`${(item.state.utilization * 100).toFixed(2)}%`}

)} From b7fca2ec9a3f1bf2257c00a7625ab0c48fe6bdc3 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sun, 30 Nov 2025 12:02:58 +0800 Subject: [PATCH 4/4] chore: simplify --- src/components/common/MarketsTableWithSameLoanAsset.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx index 1279b73e..38dcfa2b 100644 --- a/src/components/common/MarketsTableWithSameLoanAsset.tsx +++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx @@ -537,13 +537,7 @@ function MarketRow({ {columnVisibility.utilizationRate && (

- {(() => { - const supplyUsd = market.state.supplyAssetsUsd; - const borrowUsd = market.state.borrowAssetsUsd; - if (!supplyUsd || supplyUsd === 0) return '—'; - const utilization = (borrowUsd / supplyUsd) * 100; - return `${utilization.toFixed(2)}%`; - })()} + {`${(market.state.utilization * 100).toFixed(2)}%`}

)}