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)}%`}
|
)}