From c10b1d24d20b729c6a317586b39881e3ab9ea1e3 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Wed, 27 May 2026 20:37:13 -0600 Subject: [PATCH 1/7] chore: translations for the market tables --- .../libs/translations/translations/en.json | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index cf233861be..f631b54389 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -1711,6 +1711,62 @@ "labelTooltip": "This is your current spending cap for this asset. To spend more, revoke the cap and set a new one." }, "statsPage": { + "eModeTable": { + "columns": { + "asset": "Asset", + "borrowable": "Borrowable", + "collateral": "Collateral", + "collateralFactor": "Collateral Factor", + "group": "E-Mode Group", + "liquidationIncentive": "Liquidation Incentive", + "liquidationThreshold": "Liquidation Threshold" + }, + "no": "No", + "noData": "No E-Mode groups configured.", + "title": "E-Mode Groups", + "unavailable": "E-Mode data unavailable.", + "yes": "Yes" + }, + "marketsTable": { + "columns": { + "asset": "Asset", + "bnbDebt": "BNB Debt", + "borrowApy": "Borrow APY", + "borrowCap": "Borrow Cap", + "borrowCapFilled": "Borrow Cap %", + "liquidity": "Liquidity", + "otherDebt": "Other Debt", + "stablecoinDebt": "Stablecoin Debt", + "supplyApy": "Supply APY", + "supplyCap": "Supply Cap", + "supplyCapFilled": "Supply Cap %", + "topBorrowerShare": "Top Borrower %", + "topSupplierShare": "Top Supplier %", + "totalBorrows": "Total Borrows", + "totalDebtAgainst": "Total Debt Against", + "totalSupply": "Total Supply", + "utilization": "Utilization" + }, + "noData": "No market snapshot yet.", + "title": "Markets", + "unavailable": "Markets data unavailable." + }, + "riskParametersTable": { + "columns": { + "asset": "Asset", + "borrowCap": "Borrow Cap", + "collateralFactor": "Collateral Factor", + "liquidationIncentive": "Liquidation Incentive", + "liquidationThreshold": "Liquidation Threshold", + "price": "Price", + "reserveFactor": "Reserve Factor", + "supplyCap": "Supply Cap", + "utilization": "Utilization" + }, + "noData": "No market snapshot yet.", + "title": "Risk Parameters by Market", + "unavailable": "Risk parameters unavailable." + }, "eModeTable": { "columns": { "asset": "Asset", From 3244204d9462ee0ac8076617230b2610027c3598 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 28 May 2026 19:56:02 -0600 Subject: [PATCH 2/7] feat: add getRiskDashboardLiquidationsDaily --- .../index.ts | 29 ++++++++++++++ .../useGetRiskDashboardLiquidationsDaily.ts | 38 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/index.ts create mode 100644 apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/useGetRiskDashboardLiquidationsDaily.ts diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/index.ts b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/index.ts new file mode 100644 index 0000000000..86e6feb058 --- /dev/null +++ b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/index.ts @@ -0,0 +1,29 @@ +import type { ChainId } from 'types'; +import { fetchRiskDashboard } from '../fetchRiskDashboard'; + +export interface ApiRiskDashboardLiquidationsDailyDay { + day: string; + count: string; + debtRepaidUsdCents: string; + collateralSeizedUsdCents: string; + activeLiquidators: string; +} + +export interface GetRiskDashboardLiquidationsDailyResponse { + chainId: string; + days: number; + series: ApiRiskDashboardLiquidationsDailyDay[]; +} + +export const getRiskDashboardLiquidationsDaily = ({ + chainId, + days, +}: { + chainId: ChainId; + days: number; +}) => + fetchRiskDashboard({ + endpoint: '/risk-dashboard/liquidations-daily', + chainId, + params: { days }, + }); diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/useGetRiskDashboardLiquidationsDaily.ts b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/useGetRiskDashboardLiquidationsDaily.ts new file mode 100644 index 0000000000..92041d555a --- /dev/null +++ b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDaily/useGetRiskDashboardLiquidationsDaily.ts @@ -0,0 +1,38 @@ +import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; + +import FunctionKey from 'constants/functionKey'; +import { useChainId } from 'libs/wallet'; +import type { ChainId } from 'types'; +import { + type GetRiskDashboardLiquidationsDailyResponse, + getRiskDashboardLiquidationsDaily, +} from '.'; + +export type UseGetRiskDashboardLiquidationsDailyQueryKey = [ + FunctionKey.GET_RISK_DASHBOARD_LIQUIDATIONS_DAILY, + { chainId: ChainId; days: number }, +]; + +type Options = QueryObserverOptions< + GetRiskDashboardLiquidationsDailyResponse, + Error, + GetRiskDashboardLiquidationsDailyResponse, + GetRiskDashboardLiquidationsDailyResponse, + UseGetRiskDashboardLiquidationsDailyQueryKey +>; + +export const useGetRiskDashboardLiquidationsDaily = ( + { days }: { days: number }, + options?: Partial, +) => { + const { chainId } = useChainId(); + + return useQuery({ + queryKey: [ + FunctionKey.GET_RISK_DASHBOARD_LIQUIDATIONS_DAILY, + { chainId, days }, + ], + queryFn: () => getRiskDashboardLiquidationsDaily({ chainId, days }), + ...options, + }); +}; From a95a101885ccc23a6f71953e83db0410e2b1852a Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 28 May 2026 19:56:12 -0600 Subject: [PATCH 3/7] feat: add getRiskDashboardLiquidationsDistribution --- .../index.ts | 33 +++++++++++++++ ...etRiskDashboardLiquidationsDistribution.ts | 40 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/index.ts create mode 100644 apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/useGetRiskDashboardLiquidationsDistribution.ts diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/index.ts b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/index.ts new file mode 100644 index 0000000000..d69a07b5c0 --- /dev/null +++ b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/index.ts @@ -0,0 +1,33 @@ +import type { ChainId } from 'types'; +import type { Address } from 'viem'; +import { fetchRiskDashboard } from '../fetchRiskDashboard'; + +export type LiquidationsDistributionAxis = 'collateral' | 'debt'; + +export interface ApiRiskDashboardLiquidationsDistributionRow { + marketAddress: Address; + count: string; + valueUsdCents: string; +} + +export interface GetRiskDashboardLiquidationsDistributionResponse { + chainId: string; + days: number; + by: LiquidationsDistributionAxis; + rows: ApiRiskDashboardLiquidationsDistributionRow[]; +} + +export const getRiskDashboardLiquidationsDistribution = ({ + chainId, + days, + by, +}: { + chainId: ChainId; + days: number; + by: LiquidationsDistributionAxis; +}) => + fetchRiskDashboard({ + endpoint: '/risk-dashboard/liquidations-distribution', + chainId, + params: { days, by }, + }); diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/useGetRiskDashboardLiquidationsDistribution.ts b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/useGetRiskDashboardLiquidationsDistribution.ts new file mode 100644 index 0000000000..5ecca831cd --- /dev/null +++ b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsDistribution/useGetRiskDashboardLiquidationsDistribution.ts @@ -0,0 +1,40 @@ +import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; + +import FunctionKey from 'constants/functionKey'; +import { useChainId } from 'libs/wallet'; +import type { ChainId } from 'types'; +import { + type GetRiskDashboardLiquidationsDistributionResponse, + type LiquidationsDistributionAxis, + getRiskDashboardLiquidationsDistribution, +} from '.'; + +export type UseGetRiskDashboardLiquidationsDistributionQueryKey = [ + FunctionKey.GET_RISK_DASHBOARD_LIQUIDATIONS_DISTRIBUTION, + { chainId: ChainId; days: number; by: LiquidationsDistributionAxis }, +]; + +type Options = QueryObserverOptions< + GetRiskDashboardLiquidationsDistributionResponse, + Error, + GetRiskDashboardLiquidationsDistributionResponse, + GetRiskDashboardLiquidationsDistributionResponse, + UseGetRiskDashboardLiquidationsDistributionQueryKey +>; + +export const useGetRiskDashboardLiquidationsDistribution = ( + { days, by }: { days: number; by: LiquidationsDistributionAxis }, + options?: Partial, +) => { + const { chainId } = useChainId(); + + return useQuery({ + queryKey: [ + FunctionKey.GET_RISK_DASHBOARD_LIQUIDATIONS_DISTRIBUTION, + { chainId, days, by }, + ], + queryFn: () => + getRiskDashboardLiquidationsDistribution({ chainId, days, by }), + ...options, + }); +}; From bf3ca631c14b900e403c3e2112892f8421482ffa Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 28 May 2026 19:56:20 -0600 Subject: [PATCH 4/7] feat: add getRiskDashboardLiquidationsSummary --- .../index.ts | 26 +++++++++++++ .../useGetRiskDashboardLiquidationsSummary.ts | 38 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/index.ts create mode 100644 apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/useGetRiskDashboardLiquidationsSummary.ts diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/index.ts b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/index.ts new file mode 100644 index 0000000000..db4158b0de --- /dev/null +++ b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/index.ts @@ -0,0 +1,26 @@ +import type { ChainId } from 'types'; +import { fetchRiskDashboard } from '../fetchRiskDashboard'; + +export interface GetRiskDashboardLiquidationsSummaryResponse { + chainId: string; + days: number; + count: string; + debtRepaidUsdCents: string; + collateralSeizedUsdCents: string; + bonusUsdCents: string; + activeLiquidators: string; + uniqueBorrowers: string; +} + +export const getRiskDashboardLiquidationsSummary = ({ + chainId, + days, +}: { + chainId: ChainId; + days: number; +}) => + fetchRiskDashboard({ + endpoint: '/risk-dashboard/liquidations-summary', + chainId, + params: { days }, + }); diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/useGetRiskDashboardLiquidationsSummary.ts b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/useGetRiskDashboardLiquidationsSummary.ts new file mode 100644 index 0000000000..ce5e34e0fe --- /dev/null +++ b/apps/evm/src/clients/api/queries/getRiskDashboardLiquidationsSummary/useGetRiskDashboardLiquidationsSummary.ts @@ -0,0 +1,38 @@ +import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; + +import FunctionKey from 'constants/functionKey'; +import { useChainId } from 'libs/wallet'; +import type { ChainId } from 'types'; +import { + type GetRiskDashboardLiquidationsSummaryResponse, + getRiskDashboardLiquidationsSummary, +} from '.'; + +export type UseGetRiskDashboardLiquidationsSummaryQueryKey = [ + FunctionKey.GET_RISK_DASHBOARD_LIQUIDATIONS_SUMMARY, + { chainId: ChainId; days: number }, +]; + +type Options = QueryObserverOptions< + GetRiskDashboardLiquidationsSummaryResponse, + Error, + GetRiskDashboardLiquidationsSummaryResponse, + GetRiskDashboardLiquidationsSummaryResponse, + UseGetRiskDashboardLiquidationsSummaryQueryKey +>; + +export const useGetRiskDashboardLiquidationsSummary = ( + { days }: { days: number }, + options?: Partial, +) => { + const { chainId } = useChainId(); + + return useQuery({ + queryKey: [ + FunctionKey.GET_RISK_DASHBOARD_LIQUIDATIONS_SUMMARY, + { chainId, days }, + ], + queryFn: () => getRiskDashboardLiquidationsSummary({ chainId, days }), + ...options, + }); +}; From c669f5c9eb29fbe11bfb64a1586a222ae5d20e1a Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 28 May 2026 20:00:59 -0600 Subject: [PATCH 5/7] chore: add function keys and export queries --- apps/evm/src/clients/api/index.ts | 6 ++++++ .../api/queries/getRiskDashboardWalletAggregates/index.ts | 1 + apps/evm/src/constants/functionKey.ts | 3 +++ 3 files changed, 10 insertions(+) diff --git a/apps/evm/src/clients/api/index.ts b/apps/evm/src/clients/api/index.ts index adffcb3766..ad8a8686d8 100644 --- a/apps/evm/src/clients/api/index.ts +++ b/apps/evm/src/clients/api/index.ts @@ -327,6 +327,12 @@ export * from './queries/getRiskDashboardMarkets'; export * from './queries/getRiskDashboardMarkets/useGetRiskDashboardMarkets'; export * from './queries/getRiskDashboardEModePools'; export * from './queries/getRiskDashboardEModePools/useGetRiskDashboardEModePools'; +export * from './queries/getRiskDashboardLiquidationsSummary'; +export * from './queries/getRiskDashboardLiquidationsSummary/useGetRiskDashboardLiquidationsSummary'; +export * from './queries/getRiskDashboardLiquidationsDaily'; +export * from './queries/getRiskDashboardLiquidationsDaily/useGetRiskDashboardLiquidationsDaily'; +export * from './queries/getRiskDashboardLiquidationsDistribution'; +export * from './queries/getRiskDashboardLiquidationsDistribution/useGetRiskDashboardLiquidationsDistribution'; export * from './queries/getRiskDashboardMarketSnapshots'; export * from './queries/getRiskDashboardMarketSnapshots/useGetRiskDashboardMarketSnapshots'; export * from './queries/getRiskDashboardWalletAggregates'; diff --git a/apps/evm/src/clients/api/queries/getRiskDashboardWalletAggregates/index.ts b/apps/evm/src/clients/api/queries/getRiskDashboardWalletAggregates/index.ts index 7206ba8de4..f1f30f6378 100644 --- a/apps/evm/src/clients/api/queries/getRiskDashboardWalletAggregates/index.ts +++ b/apps/evm/src/clients/api/queries/getRiskDashboardWalletAggregates/index.ts @@ -19,6 +19,7 @@ export interface GetRiskDashboardWalletAggregatesResponse { valueEligibleForLiquidationUsdCents: string; totalCollateralAtRiskUsdCents: string; totalBadDebtUsdCents: string; + walletsWithBadDebt: string; } const HEALTH_FACTOR_AT_RISK_DECIMAL = 1.125; diff --git a/apps/evm/src/constants/functionKey.ts b/apps/evm/src/constants/functionKey.ts index 4ce8245eba..f455ca98e6 100644 --- a/apps/evm/src/constants/functionKey.ts +++ b/apps/evm/src/constants/functionKey.ts @@ -106,6 +106,9 @@ enum FunctionKey { GET_RISK_DASHBOARD_WALLET_AGGREGATES = 'GET_RISK_DASHBOARD_WALLET_AGGREGATES', GET_RISK_DASHBOARD_TOP_WALLETS = 'GET_RISK_DASHBOARD_TOP_WALLETS', GET_RISK_DASHBOARD_TRANSACTIONS_VOLUME = 'GET_RISK_DASHBOARD_TRANSACTIONS_VOLUME', + GET_RISK_DASHBOARD_LIQUIDATIONS_SUMMARY = 'GET_RISK_DASHBOARD_LIQUIDATIONS_SUMMARY', + GET_RISK_DASHBOARD_LIQUIDATIONS_DAILY = 'GET_RISK_DASHBOARD_LIQUIDATIONS_DAILY', + GET_RISK_DASHBOARD_LIQUIDATIONS_DISTRIBUTION = 'GET_RISK_DASHBOARD_LIQUIDATIONS_DISTRIBUTION', GET_IMAGE_ACCENT_COLOR = 'GET_IMAGE_ACCENT_COLOR', } From 152941c8c5d789e8c9e2f1db23442ecb7d7f2751 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 28 May 2026 20:01:25 -0600 Subject: [PATCH 6/7] chore: translation keys for liquidations --- .../libs/translations/translations/en.json | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index f631b54389..1c4e9aa19b 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -1751,6 +1751,39 @@ "title": "Markets", "unavailable": "Markets data unavailable." }, + "liquidations": { + "dailyChart": { + "noData": "No liquidations in the selected period.", + "title": "Liquidations Activity", + "tooltip": { + "activeLiquidators": "Active Liquidators", + "collateralSeized": "Collateral Seized", + "count": "Liquidations", + "debtRepaid": "Debt Repaid" + }, + "unavailable": "Daily liquidations unavailable." + }, + "distributionChart": { + "byCollateralTitle": "Liquidations by Collateral Asset", + "byDebtTitle": "Liquidations by Debt Asset", + "noData": "No liquidations in the selected period.", + "unavailable": "Distribution unavailable." + }, + "kpis": { + "activeLiquidators": "Active Liquidators", + "bonus": "Liquidation Bonus", + "collateralSeized": "Collateral Seized", + "count": "Liquidations", + "debtRepaid": "Debt Repaid", + "unavailable": "Liquidations data unavailable.", + "walletsWithBadDebt": "Wallets with Bad Debt" + }, + "period": { + "24h": "24h", + "30d": "30d", + "7d": "7d" + } + }, "riskParametersTable": { "columns": { "asset": "Asset", From b86e57fa7da10ed7eaceb099e89491ff1965beda Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 28 May 2026 20:01:43 -0600 Subject: [PATCH 7/7] feat: add liquidations tab --- .../libs/translations/translations/en.json | 124 ++++-------- .../libs/translations/translations/ja.json | 37 ++++ .../libs/translations/translations/th.json | 37 ++++ .../libs/translations/translations/tr.json | 37 ++++ .../libs/translations/translations/vi.json | 37 ++++ .../translations/translations/zh-Hans.json | 37 ++++ .../translations/translations/zh-Hant.json | 37 ++++ .../LiquidationsDailyChart/index.tsx | 149 ++++++++++++++ .../LiquidationsDistributionChart/index.tsx | 187 ++++++++++++++++++ .../Liquidations/LiquidationsKpis/index.tsx | 76 +++++++ .../src/pages/Stats/Liquidations/index.tsx | 39 ++++ apps/evm/src/pages/Stats/index.tsx | 3 +- 12 files changed, 711 insertions(+), 89 deletions(-) create mode 100644 apps/evm/src/pages/Stats/Liquidations/LiquidationsDailyChart/index.tsx create mode 100644 apps/evm/src/pages/Stats/Liquidations/LiquidationsDistributionChart/index.tsx create mode 100644 apps/evm/src/pages/Stats/Liquidations/LiquidationsKpis/index.tsx create mode 100644 apps/evm/src/pages/Stats/Liquidations/index.tsx diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index 1c4e9aa19b..b4d0899e2b 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -1727,30 +1727,40 @@ "unavailable": "E-Mode data unavailable.", "yes": "Yes" }, - "marketsTable": { - "columns": { - "asset": "Asset", - "bnbDebt": "BNB Debt", - "borrowApy": "Borrow APY", - "borrowCap": "Borrow Cap", - "borrowCapFilled": "Borrow Cap %", - "liquidity": "Liquidity", - "otherDebt": "Other Debt", - "stablecoinDebt": "Stablecoin Debt", - "supplyApy": "Supply APY", - "supplyCap": "Supply Cap", - "supplyCapFilled": "Supply Cap %", - "topBorrowerShare": "Top Borrower %", - "topSupplierShare": "Top Supplier %", - "totalBorrows": "Total Borrows", - "totalDebtAgainst": "Total Debt Against", - "totalSupply": "Total Supply", - "utilization": "Utilization" + "historicalDominance": { + "borrows": { + "noData": "No historical snapshots yet.", + "ratioLabel": "Borrows / Liquidity", + "title": "Borrow Dominance over Time", + "unavailable": "Borrow dominance unavailable." }, - "noData": "No market snapshot yet.", - "title": "Markets", - "unavailable": "Markets data unavailable." + "supply": { + "noData": "No historical snapshots yet.", + "ratioLabel": "Supply / Liquidity", + "title": "Supply Dominance over Time", + "unavailable": "Supply dominance unavailable." + } + }, + "historicalLiquidity": { + "noData": "No historical snapshots yet.", + "title": "Liquidity over Time", + "tooltipLabel": "Liquidity", + "unavailable": "Historical liquidity unavailable." + }, + "historicalMarketChart": { + "borrows": { + "noData": "No historical snapshots yet.", + "title": "Borrows by Assets over Time", + "unavailable": "Historical borrows unavailable." + }, + "others": "Others", + "supply": { + "noData": "No historical snapshots yet.", + "title": "Deposits by Assets over Time", + "unavailable": "Historical supply unavailable." + } }, + "illustrationAlt": "Stats illustration", "liquidations": { "dailyChart": { "noData": "No liquidations in the selected period.", @@ -1767,6 +1777,10 @@ "byCollateralTitle": "Liquidations by Collateral Asset", "byDebtTitle": "Liquidations by Debt Asset", "noData": "No liquidations in the selected period.", + "tooltip": { + "count": "Count", + "value": "Value" + }, "unavailable": "Distribution unavailable." }, "kpis": { @@ -1784,72 +1798,6 @@ "7d": "7d" } }, - "riskParametersTable": { - "columns": { - "asset": "Asset", - "borrowCap": "Borrow Cap", - "collateralFactor": "Collateral Factor", - "liquidationIncentive": "Liquidation Incentive", - "liquidationThreshold": "Liquidation Threshold", - "price": "Price", - "reserveFactor": "Reserve Factor", - "supplyCap": "Supply Cap", - "utilization": "Utilization" - }, - "noData": "No market snapshot yet.", - "title": "Risk Parameters by Market", - "unavailable": "Risk parameters unavailable." - }, - "eModeTable": { - "columns": { - "asset": "Asset", - "borrowable": "Borrowable", - "collateral": "Collateral", - "collateralFactor": "Collateral Factor", - "group": "E-Mode Group", - "liquidationIncentive": "Liquidation Incentive", - "liquidationThreshold": "Liquidation Threshold" - }, - "no": "No", - "noData": "No E-Mode groups configured.", - "title": "E-Mode Groups", - "unavailable": "E-Mode data unavailable.", - "yes": "Yes" - }, - "historicalDominance": { - "borrows": { - "noData": "No historical snapshots yet.", - "ratioLabel": "Borrows / Liquidity", - "title": "Borrow Dominance over Time", - "unavailable": "Borrow dominance unavailable." - }, - "supply": { - "noData": "No historical snapshots yet.", - "ratioLabel": "Supply / Liquidity", - "title": "Supply Dominance over Time", - "unavailable": "Supply dominance unavailable." - } - }, - "historicalLiquidity": { - "noData": "No historical snapshots yet.", - "title": "Liquidity over Time", - "tooltipLabel": "Liquidity", - "unavailable": "Historical liquidity unavailable." - }, - "historicalMarketChart": { - "borrows": { - "noData": "No historical snapshots yet.", - "title": "Borrows by Assets over Time", - "unavailable": "Historical borrows unavailable." - }, - "others": "Others", - "supply": { - "noData": "No historical snapshots yet.", - "title": "Deposits by Assets over Time", - "unavailable": "Historical supply unavailable." - } - }, - "illustrationAlt": "Stats illustration", "marketKpis": { "liquidity": "Liquidity", "totalBorrows": "Total Borrows", diff --git a/apps/evm/src/libs/translations/translations/ja.json b/apps/evm/src/libs/translations/translations/ja.json index 043a3447b6..beec307d18 100644 --- a/apps/evm/src/libs/translations/translations/ja.json +++ b/apps/evm/src/libs/translations/translations/ja.json @@ -1761,6 +1761,43 @@ } }, "illustrationAlt": "統計イラスト", + "liquidations": { + "dailyChart": { + "noData": "TRANSLATION NEEDED", + "title": "TRANSLATION NEEDED", + "tooltip": { + "activeLiquidators": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "distributionChart": { + "byCollateralTitle": "TRANSLATION NEEDED", + "byDebtTitle": "TRANSLATION NEEDED", + "noData": "TRANSLATION NEEDED", + "tooltip": { + "count": "TRANSLATION NEEDED", + "value": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "kpis": { + "activeLiquidators": "TRANSLATION NEEDED", + "bonus": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED", + "unavailable": "TRANSLATION NEEDED", + "walletsWithBadDebt": "TRANSLATION NEEDED" + }, + "period": { + "24h": "TRANSLATION NEEDED", + "30d": "TRANSLATION NEEDED", + "7d": "TRANSLATION NEEDED" + } + }, "marketKpis": { "liquidity": "TRANSLATION NEEDED", "totalBorrows": "TRANSLATION NEEDED", diff --git a/apps/evm/src/libs/translations/translations/th.json b/apps/evm/src/libs/translations/translations/th.json index 59b471b4cf..79753450f5 100644 --- a/apps/evm/src/libs/translations/translations/th.json +++ b/apps/evm/src/libs/translations/translations/th.json @@ -1761,6 +1761,43 @@ } }, "illustrationAlt": "ภาพประกอบสถิติ", + "liquidations": { + "dailyChart": { + "noData": "TRANSLATION NEEDED", + "title": "TRANSLATION NEEDED", + "tooltip": { + "activeLiquidators": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "distributionChart": { + "byCollateralTitle": "TRANSLATION NEEDED", + "byDebtTitle": "TRANSLATION NEEDED", + "noData": "TRANSLATION NEEDED", + "tooltip": { + "count": "TRANSLATION NEEDED", + "value": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "kpis": { + "activeLiquidators": "TRANSLATION NEEDED", + "bonus": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED", + "unavailable": "TRANSLATION NEEDED", + "walletsWithBadDebt": "TRANSLATION NEEDED" + }, + "period": { + "24h": "TRANSLATION NEEDED", + "30d": "TRANSLATION NEEDED", + "7d": "TRANSLATION NEEDED" + } + }, "marketKpis": { "liquidity": "TRANSLATION NEEDED", "totalBorrows": "TRANSLATION NEEDED", diff --git a/apps/evm/src/libs/translations/translations/tr.json b/apps/evm/src/libs/translations/translations/tr.json index 9d968d6d25..fa707bc013 100644 --- a/apps/evm/src/libs/translations/translations/tr.json +++ b/apps/evm/src/libs/translations/translations/tr.json @@ -1761,6 +1761,43 @@ } }, "illustrationAlt": "İstatistik illüstrasyonu", + "liquidations": { + "dailyChart": { + "noData": "TRANSLATION NEEDED", + "title": "TRANSLATION NEEDED", + "tooltip": { + "activeLiquidators": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "distributionChart": { + "byCollateralTitle": "TRANSLATION NEEDED", + "byDebtTitle": "TRANSLATION NEEDED", + "noData": "TRANSLATION NEEDED", + "tooltip": { + "count": "TRANSLATION NEEDED", + "value": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "kpis": { + "activeLiquidators": "TRANSLATION NEEDED", + "bonus": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED", + "unavailable": "TRANSLATION NEEDED", + "walletsWithBadDebt": "TRANSLATION NEEDED" + }, + "period": { + "24h": "TRANSLATION NEEDED", + "30d": "TRANSLATION NEEDED", + "7d": "TRANSLATION NEEDED" + } + }, "marketKpis": { "liquidity": "TRANSLATION NEEDED", "totalBorrows": "TRANSLATION NEEDED", diff --git a/apps/evm/src/libs/translations/translations/vi.json b/apps/evm/src/libs/translations/translations/vi.json index e10b2c01e0..f9855f7161 100644 --- a/apps/evm/src/libs/translations/translations/vi.json +++ b/apps/evm/src/libs/translations/translations/vi.json @@ -1761,6 +1761,43 @@ } }, "illustrationAlt": "Hình minh họa thống kê", + "liquidations": { + "dailyChart": { + "noData": "TRANSLATION NEEDED", + "title": "TRANSLATION NEEDED", + "tooltip": { + "activeLiquidators": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "distributionChart": { + "byCollateralTitle": "TRANSLATION NEEDED", + "byDebtTitle": "TRANSLATION NEEDED", + "noData": "TRANSLATION NEEDED", + "tooltip": { + "count": "TRANSLATION NEEDED", + "value": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "kpis": { + "activeLiquidators": "TRANSLATION NEEDED", + "bonus": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED", + "unavailable": "TRANSLATION NEEDED", + "walletsWithBadDebt": "TRANSLATION NEEDED" + }, + "period": { + "24h": "TRANSLATION NEEDED", + "30d": "TRANSLATION NEEDED", + "7d": "TRANSLATION NEEDED" + } + }, "marketKpis": { "liquidity": "TRANSLATION NEEDED", "totalBorrows": "TRANSLATION NEEDED", diff --git a/apps/evm/src/libs/translations/translations/zh-Hans.json b/apps/evm/src/libs/translations/translations/zh-Hans.json index f8a27b5129..01e0f0893a 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hans.json +++ b/apps/evm/src/libs/translations/translations/zh-Hans.json @@ -1761,6 +1761,43 @@ } }, "illustrationAlt": "统计插图", + "liquidations": { + "dailyChart": { + "noData": "TRANSLATION NEEDED", + "title": "TRANSLATION NEEDED", + "tooltip": { + "activeLiquidators": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "distributionChart": { + "byCollateralTitle": "TRANSLATION NEEDED", + "byDebtTitle": "TRANSLATION NEEDED", + "noData": "TRANSLATION NEEDED", + "tooltip": { + "count": "TRANSLATION NEEDED", + "value": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "kpis": { + "activeLiquidators": "TRANSLATION NEEDED", + "bonus": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED", + "unavailable": "TRANSLATION NEEDED", + "walletsWithBadDebt": "TRANSLATION NEEDED" + }, + "period": { + "24h": "TRANSLATION NEEDED", + "30d": "TRANSLATION NEEDED", + "7d": "TRANSLATION NEEDED" + } + }, "marketKpis": { "liquidity": "TRANSLATION NEEDED", "totalBorrows": "TRANSLATION NEEDED", diff --git a/apps/evm/src/libs/translations/translations/zh-Hant.json b/apps/evm/src/libs/translations/translations/zh-Hant.json index dd0c6bab28..7aaff96bd7 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hant.json +++ b/apps/evm/src/libs/translations/translations/zh-Hant.json @@ -1761,6 +1761,43 @@ } }, "illustrationAlt": "統計插圖", + "liquidations": { + "dailyChart": { + "noData": "TRANSLATION NEEDED", + "title": "TRANSLATION NEEDED", + "tooltip": { + "activeLiquidators": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "distributionChart": { + "byCollateralTitle": "TRANSLATION NEEDED", + "byDebtTitle": "TRANSLATION NEEDED", + "noData": "TRANSLATION NEEDED", + "tooltip": { + "count": "TRANSLATION NEEDED", + "value": "TRANSLATION NEEDED" + }, + "unavailable": "TRANSLATION NEEDED" + }, + "kpis": { + "activeLiquidators": "TRANSLATION NEEDED", + "bonus": "TRANSLATION NEEDED", + "collateralSeized": "TRANSLATION NEEDED", + "count": "TRANSLATION NEEDED", + "debtRepaid": "TRANSLATION NEEDED", + "unavailable": "TRANSLATION NEEDED", + "walletsWithBadDebt": "TRANSLATION NEEDED" + }, + "period": { + "24h": "TRANSLATION NEEDED", + "30d": "TRANSLATION NEEDED", + "7d": "TRANSLATION NEEDED" + } + }, "marketKpis": { "liquidity": "TRANSLATION NEEDED", "totalBorrows": "TRANSLATION NEEDED", diff --git a/apps/evm/src/pages/Stats/Liquidations/LiquidationsDailyChart/index.tsx b/apps/evm/src/pages/Stats/Liquidations/LiquidationsDailyChart/index.tsx new file mode 100644 index 0000000000..0bfe5aaf44 --- /dev/null +++ b/apps/evm/src/pages/Stats/Liquidations/LiquidationsDailyChart/index.tsx @@ -0,0 +1,149 @@ +import { theme } from '@venusprotocol/ui'; +import { + type ApiRiskDashboardLiquidationsDailyDay, + useGetRiskDashboardLiquidationsDaily, +} from 'clients/api'; +import { Card, Spinner } from 'components'; +import { useTranslation } from 'libs/translations'; +import { + Bar, + CartesianGrid, + BarChart as RCBarChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { formatCentsToReadableValue } from 'utilities'; +import { roundUpToScale } from '../../roundUpToScale'; + +interface ChartDatum { + day: string; + count: number; + debtRepaidUsdCents: string; + collateralSeizedUsdCents: string; + activeLiquidators: string; +} + +const formatDayShort = (day: string) => { + const date = new Date(`${day}T00:00:00Z`); + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); +}; + +const buildChartData = (series: ApiRiskDashboardLiquidationsDailyDay[]): ChartDatum[] => + series.map(day => ({ + day: day.day, + count: Number(day.count), + debtRepaidUsdCents: day.debtRepaidUsdCents, + collateralSeizedUsdCents: day.collateralSeizedUsdCents, + activeLiquidators: day.activeLiquidators, + })); + +interface TooltipPayloadItem { + payload: ChartDatum; +} + +const DailyTooltip: React.FC<{ + active?: boolean; + payload?: TooltipPayloadItem[]; +}> = ({ active, payload }) => { + const { t } = useTranslation(); + + if (!active || !payload || payload.length === 0) { + return null; + } + const datum = payload[0].payload; + + return ( +
+

{formatDayShort(datum.day)}

+
+ + {t('statsPage.liquidations.dailyChart.tooltip.count')} + + {datum.count} +
+
+ + {t('statsPage.liquidations.dailyChart.tooltip.debtRepaid')} + + + {formatCentsToReadableValue({ value: datum.debtRepaidUsdCents })} + +
+
+ + {t('statsPage.liquidations.dailyChart.tooltip.collateralSeized')} + + + {formatCentsToReadableValue({ value: datum.collateralSeizedUsdCents })} + +
+
+ + {t('statsPage.liquidations.dailyChart.tooltip.activeLiquidators')} + + {datum.activeLiquidators} +
+
+ ); +}; + +export interface LiquidationsDailyChartProps { + days: number; +} + +export const LiquidationsDailyChart: React.FC = ({ days }) => { + const { t } = useTranslation(); + const { data, isLoading, isError } = useGetRiskDashboardLiquidationsDaily({ days }); + const chartData = data ? buildChartData(data.series) : []; + + return ( + +

{t('statsPage.liquidations.dailyChart.title')}

+ +
+ {isLoading ? ( + + ) : isError || !data ? ( +

{t('statsPage.liquidations.dailyChart.unavailable')}

+ ) : chartData.length === 0 ? ( +

{t('statsPage.liquidations.dailyChart.noData')}

+ ) : ( + + + + + + + + + } + /> + + + + + )} +
+
+ ); +}; diff --git a/apps/evm/src/pages/Stats/Liquidations/LiquidationsDistributionChart/index.tsx b/apps/evm/src/pages/Stats/Liquidations/LiquidationsDistributionChart/index.tsx new file mode 100644 index 0000000000..6a81e1441b --- /dev/null +++ b/apps/evm/src/pages/Stats/Liquidations/LiquidationsDistributionChart/index.tsx @@ -0,0 +1,187 @@ +import { theme } from '@venusprotocol/ui'; +import { + type ApiRiskDashboardLiquidationsDistributionRow, + type LiquidationsDistributionAxis, + useGetRiskDashboardLiquidationsDistribution, +} from 'clients/api'; +import { Card, Spinner } from 'components'; +import { useGetVTokens } from 'libs/tokens/hooks/useGetVTokens'; +import { useTranslation } from 'libs/translations'; +import { useMemo } from 'react'; +import { + Bar, + CartesianGrid, + Cell, + BarChart as RCBarChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { formatCentsToReadableValue, truncateAddress } from 'utilities'; +import { getAddress } from 'viem'; +import { roundUpToScale } from '../../roundUpToScale'; +import { useMarketColors } from '../../useMarketColors'; + +interface ChartDatum { + marketAddress: string; + symbol: string; + valueDollars: number; + count: string; + valueUsdCents: string; +} + +const toDollars = (cents: string) => Number(cents) / 100; + +const buildChartData = ( + rows: ApiRiskDashboardLiquidationsDistributionRow[], + symbolByMarket: Map, +): ChartDatum[] => + [...rows] + .sort((a, b) => Number(b.valueUsdCents) - Number(a.valueUsdCents)) + .map(row => ({ + marketAddress: row.marketAddress, + symbol: symbolByMarket.get(row.marketAddress) ?? truncateAddress(row.marketAddress), + valueDollars: toDollars(row.valueUsdCents), + count: row.count, + valueUsdCents: row.valueUsdCents, + })); + +interface TooltipPayloadItem { + payload: ChartDatum; +} + +const DistributionTooltip: React.FC<{ + active?: boolean; + payload?: TooltipPayloadItem[]; +}> = ({ active, payload }) => { + const { t } = useTranslation(); + + if (!active || !payload || payload.length === 0) { + return null; + } + const datum = payload[0].payload; + + return ( +
+

{datum.symbol}

+
+ + {t('statsPage.liquidations.distributionChart.tooltip.value')} + + + {formatCentsToReadableValue({ value: datum.valueUsdCents })} + +
+
+ + {t('statsPage.liquidations.distributionChart.tooltip.count')} + + {datum.count} +
+
+ ); +}; + +export interface LiquidationsDistributionChartProps { + days: number; + by: LiquidationsDistributionAxis; +} + +export const LiquidationsDistributionChart: React.FC = ({ + days, + by, +}) => { + const { t } = useTranslation(); + const vTokens = useGetVTokens(); + const { data, isLoading, isError } = useGetRiskDashboardLiquidationsDistribution({ days, by }); + const { colorByMarket } = useMarketColors(); + + const symbolByMarket = useMemo(() => { + const map = new Map(); + for (const vToken of vTokens) { + map.set(getAddress(vToken.address), vToken.underlyingToken.symbol); + } + return map; + }, [vTokens]); + + const chartData = useMemo( + () => (data ? buildChartData(data.rows, symbolByMarket) : []), + [data, symbolByMarket], + ); + + // t('statsPage.liquidations.distributionChart.byCollateralTitle') + // t('statsPage.liquidations.distributionChart.byDebtTitle') + const title = t( + by === 'collateral' + ? 'statsPage.liquidations.distributionChart.byCollateralTitle' + : 'statsPage.liquidations.distributionChart.byDebtTitle', + ); + + return ( + +

{title}

+ +
+ {isLoading ? ( + + ) : isError || !data ? ( +

{t('statsPage.liquidations.distributionChart.unavailable')}

+ ) : chartData.length === 0 ? ( +

{t('statsPage.liquidations.distributionChart.noData')}

+ ) : ( + + + + + + formatCentsToReadableValue({ value: Math.round(value * 100) }) + } + /> + + + + } + /> + + + {chartData.map(datum => ( + + ))} + + + + )} +
+
+ ); +}; diff --git a/apps/evm/src/pages/Stats/Liquidations/LiquidationsKpis/index.tsx b/apps/evm/src/pages/Stats/Liquidations/LiquidationsKpis/index.tsx new file mode 100644 index 0000000000..daad67c7c2 --- /dev/null +++ b/apps/evm/src/pages/Stats/Liquidations/LiquidationsKpis/index.tsx @@ -0,0 +1,76 @@ +import { + useGetRiskDashboardLiquidationsSummary, + useGetRiskDashboardWalletAggregates, +} from 'clients/api'; +import { Card, Spinner } from 'components'; +import { useTranslation } from 'libs/translations'; +import { formatCentsToReadableValue } from 'utilities'; + +interface KpiCellProps { + label: string; + value: string; +} + +const KpiCell: React.FC = ({ label, value }) => ( +
+ {label} + {value} +
+); + +const formatInteger = (value: string) => Number.parseInt(value, 10).toLocaleString('en-US'); + +export interface LiquidationsKpisProps { + days: number; +} + +export const LiquidationsKpis: React.FC = ({ days }) => { + const { t } = useTranslation(); + const summary = useGetRiskDashboardLiquidationsSummary({ days }); + const walletAggregates = useGetRiskDashboardWalletAggregates(); + + if (summary.isLoading || walletAggregates.isLoading) { + return ( +
+ +
+ ); + } + + if (summary.isError || !summary.data) { + return

{t('statsPage.liquidations.kpis.unavailable')}

; + } + + return ( + +
+ + + + + + +
+
+ ); +}; diff --git a/apps/evm/src/pages/Stats/Liquidations/index.tsx b/apps/evm/src/pages/Stats/Liquidations/index.tsx new file mode 100644 index 0000000000..c5d2ed2ba0 --- /dev/null +++ b/apps/evm/src/pages/Stats/Liquidations/index.tsx @@ -0,0 +1,39 @@ +import { ButtonGroup } from 'components'; +import { useTranslation } from 'libs/translations'; +import { useState } from 'react'; +import { LiquidationsDailyChart } from './LiquidationsDailyChart'; +import { LiquidationsDistributionChart } from './LiquidationsDistributionChart'; +import { LiquidationsKpis } from './LiquidationsKpis'; + +// t('statsPage.liquidations.period.24h') +// t('statsPage.liquidations.period.7d') +// t('statsPage.liquidations.period.30d') +const PERIOD_OPTIONS = [ + { days: 1, labelKey: 'statsPage.liquidations.period.24h' }, + { days: 7, labelKey: 'statsPage.liquidations.period.7d' }, + { days: 30, labelKey: 'statsPage.liquidations.period.30d' }, +] as const; +const DEFAULT_PERIOD_INDEX = PERIOD_OPTIONS.length - 1; + +export const Liquidations: React.FC = () => { + const { t } = useTranslation(); + const [activeIndex, setActiveIndex] = useState(DEFAULT_PERIOD_INDEX); + const days = PERIOD_OPTIONS[activeIndex].days; + + return ( +
+ t(option.labelKey))} + activeButtonIndex={activeIndex} + onButtonClick={setActiveIndex} + /> + + +
+ + +
+
+ ); +}; diff --git a/apps/evm/src/pages/Stats/index.tsx b/apps/evm/src/pages/Stats/index.tsx index 2ef3276835..bec0250fa5 100644 --- a/apps/evm/src/pages/Stats/index.tsx +++ b/apps/evm/src/pages/Stats/index.tsx @@ -2,6 +2,7 @@ import { Page, Tabs } from 'components'; import { useTranslation } from 'libs/translations'; import { EModeTable } from './EModeTable'; import { Header } from './Header'; +import { Liquidations } from './Liquidations'; import { HistoricalDominanceChart } from './HistoricalDominanceChart'; import { HistoricalLiquidityChart } from './HistoricalLiquidityChart'; import { HistoricalMarketChart } from './HistoricalMarketChart'; @@ -53,7 +54,7 @@ const Stats: React.FC = () => { { id: 'overview', title: t('statsPage.tabs.overview'), content: }, { id: 'markets', title: t('statsPage.tabs.markets'), content: }, { id: 'wallets', title: t('statsPage.tabs.wallets'), content: null }, - { id: 'liquidations', title: t('statsPage.tabs.liquidations'), content: null }, + { id: 'liquidations', title: t('statsPage.tabs.liquidations'), content: }, ]} />