diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx
new file mode 100644
index 00000000..ef7dbf22
--- /dev/null
+++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx
@@ -0,0 +1,168 @@
+import { useMemo, useState } from 'react';
+import { Link, Pagination } from '@nextui-org/react';
+import { Table, TableHeader, TableBody, TableColumn, TableRow, TableCell } from '@nextui-org/table';
+import { ExternalLinkIcon } from '@radix-ui/react-icons';
+import moment from 'moment';
+import Image from 'next/image';
+import { Address, formatUnits } from 'viem';
+import AccountWithAvatar from '@/components/Account/AccountWithAvatar';
+import { MarketLiquidationTransaction } from '@/hooks/useMarketLiquidations';
+import { getExplorerTxURL, getExplorerURL } from '@/utils/external';
+import { findToken } from '@/utils/tokens';
+import { Market } from '@/utils/types';
+
+// Helper functions to format data
+const formatAddress = (address: string) => {
+ return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
+};
+
+type LiquidationsTableProps = {
+ chainId: number;
+ liquidations: MarketLiquidationTransaction[];
+ loading: boolean;
+ error: string | null;
+ market: Market;
+};
+
+export function LiquidationsTable({
+ chainId,
+ liquidations,
+ loading,
+ error,
+ market,
+}: LiquidationsTableProps) {
+ const [currentPage, setCurrentPage] = useState(1);
+ const pageSize = 8;
+ const totalPages = Math.ceil(liquidations.length / pageSize);
+
+ const handlePageChange = (page: number) => {
+ setCurrentPage(page);
+ };
+
+ const paginatedLiquidations = useMemo(() => {
+ const sliced = liquidations.slice((currentPage - 1) * pageSize, currentPage * pageSize);
+ return sliced;
+ }, [currentPage, liquidations, pageSize]);
+
+ const tableKey = `liquidations-table-${currentPage}`;
+
+ const collateralToken = useMemo(() => {
+ if (!market) return null;
+ return findToken(market.collateralAsset.address, chainId);
+ }, [market, chainId]);
+
+ const loanToken = useMemo(() => {
+ if (!market) return null;
+ return findToken(market.loanAsset.address, chainId);
+ }, [market, chainId]);
+
+ if (error) {
+ return
Error loading liquidations: {error}
;
+ }
+
+ return (
+
+
Liquidations
+
+
1 ? (
+
+ ) : null
+ }
+ >
+
+ Liquidator
+ Repaid ({market?.loanAsset?.symbol ?? 'USDC'})
+
+ Seized{' '}
+ {market?.collateralAsset?.symbol && (
+ {market.collateralAsset.symbol}
+ )}
+
+ Time
+ Transaction
+
+
+ {paginatedLiquidations.map((liquidation) => (
+
+
+
+
+
+
+
+
+ {formatUnits(BigInt(liquidation.data.repaidAssets), loanToken?.decimals ?? 6)}
+ {market?.loanAsset?.symbol && (
+
+ {loanToken?.img && (
+
+ )}
+
+ )}
+
+
+ {formatUnits(
+ BigInt(liquidation.data.seizedAssets),
+ collateralToken?.decimals ?? 18,
+ )}
+ {market?.collateralAsset?.symbol && (
+
+ {collateralToken?.img && (
+
+ )}
+
+ )}
+
+ {moment.unix(liquidation.timestamp).fromNow()}
+
+
+ {formatAddress(liquidation.hash)}
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/market/[chainId]/[marketid]/content.tsx b/app/market/[chainId]/[marketid]/content.tsx
index 850d4fa2..c7607e20 100644
--- a/app/market/[chainId]/[marketid]/content.tsx
+++ b/app/market/[chainId]/[marketid]/content.tsx
@@ -14,12 +14,14 @@ import Header from '@/components/layout/header/Header';
import OracleVendorBadge from '@/components/OracleVendorBadge';
import { SupplyModal } from '@/components/supplyModal';
import { useMarket, useMarketHistoricalData } from '@/hooks/useMarket';
+import useMarketLiquidations from '@/hooks/useMarketLiquidations';
import MORPHO_LOGO from '@/imgs/tokens/morpho.svg';
import { getExplorerURL, getMarketURL } from '@/utils/external';
import { getIRMTitle } from '@/utils/morpho';
import { getNetworkImg, getNetworkName, SupportedNetworks } from '@/utils/networks';
import { findToken } from '@/utils/tokens';
import { TimeseriesOptions } from '@/utils/types';
+import { LiquidationsTable } from './components/LiquidationsTable';
import RateChart from './RateChart';
import VolumeChart from './VolumeChart';
@@ -55,12 +57,19 @@ function MarketContent() {
isLoading: isMarketLoading,
error: marketError,
} = useMarket(marketid as string, network);
+
const {
data: historicalData,
isLoading: isHistoricalLoading,
refetch: refetchHistoricalData,
} = useMarketHistoricalData(marketid as string, network, rateTimeRange, volumeTimeRange);
+ const {
+ liquidations,
+ loading: liquidationsLoading,
+ error: liquidationsError,
+ } = useMarketLiquidations(market?.uniqueKey);
+
const setTimeRangeAndRefetch = useCallback(
(days: number, type: 'rate' | 'volume') => {
const endTimestamp = Math.floor(Date.now() / 1000);
@@ -321,6 +330,14 @@ function MarketContent() {
setApyTimeframe={setApyTimeframe}
setTimeRangeAndRefetch={setTimeRangeAndRefetch}
/>
+
+
>
);
diff --git a/docs/Styling.md b/docs/Styling.md
index 97835694..97979624 100644
--- a/docs/Styling.md
+++ b/docs/Styling.md
@@ -7,7 +7,6 @@ Use these shared components instead of raw HTML elements:
- `Button`: Import from `@/components/common/Button` for all clickable actions
- `Modal`: For all modal dialogs
- `Card`: For contained content sections
-- `Typography`: For text elements
## Component Guidelines
diff --git a/src/components/Account/AccountWithAvatar.tsx b/src/components/Account/AccountWithAvatar.tsx
new file mode 100644
index 00000000..e14e5b71
--- /dev/null
+++ b/src/components/Account/AccountWithAvatar.tsx
@@ -0,0 +1,20 @@
+import { Address } from 'viem';
+import { Avatar } from '@/components/Avatar/Avatar';
+import { getSlicedAddress } from '@/utils/address';
+
+type AccountWithAvatarProps = {
+ address: Address;
+};
+
+function AccountWithSmallAvatar({ address }: AccountWithAvatarProps) {
+ return (
+
+
+
+ {getSlicedAddress(address as `0x${string}`)}
+
+
+ );
+}
+
+export default AccountWithSmallAvatar;
diff --git a/src/components/Account/AccountWithENS.tsx b/src/components/Account/AccountWithENS.tsx
new file mode 100644
index 00000000..77ca856e
--- /dev/null
+++ b/src/components/Account/AccountWithENS.tsx
@@ -0,0 +1,26 @@
+import { Address } from 'viem';
+import { Avatar } from '@/components/Avatar/Avatar';
+import { getSlicedAddress } from '@/utils/address';
+import { Name } from '../common/Name';
+
+type AccountWithENSProps = {
+ address: Address;
+};
+
+function AccountWithENS({ address }: AccountWithENSProps) {
+ return (
+
+
+
+
+
+
+
+ {getSlicedAddress(address)}
+
+
+
+ );
+}
+
+export default AccountWithENS;
diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx
index df0c0a34..c9b5bb54 100644
--- a/src/components/layout/header/AccountDropdown.tsx
+++ b/src/components/layout/header/AccountDropdown.tsx
@@ -5,10 +5,9 @@ import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@nextui-o
import { ExitIcon, ExternalLinkIcon, CopyIcon } from '@radix-ui/react-icons';
import { clsx } from 'clsx';
import { useAccount, useDisconnect } from 'wagmi';
+import AccountWithENS from '@/components/Account/AccountWithENS';
import { Avatar } from '@/components/Avatar/Avatar';
-import { Name } from '@/components/common/Name';
import { useStyledToast } from '@/hooks/useStyledToast';
-import { getSlicedAddress } from '@/utils/address';
import { getExplorerURL } from '@/utils/external';
export function AccountDropdown() {
@@ -56,17 +55,7 @@ export function AccountDropdown() {
>
-
-
-
-
-
-
-
- {getSlicedAddress(address)}
-
-
-
+
diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts
index 001a62d9..dbfc707c 100644
--- a/src/graphql/queries.ts
+++ b/src/graphql/queries.ts
@@ -271,3 +271,35 @@ export const userTransactionsQuery = `
}
}
`;
+
+export const marketLiquidationsQuery = `
+ query getMarketLiquidations($uniqueKey: String!, $first: Int, $skip: Int) {
+ transactions (where: {
+ marketUniqueKey_in: [$uniqueKey],
+ type_in: [MarketLiquidation]
+ },
+ first: $first,
+ skip: $skip
+ ) {
+ items {
+ hash
+ timestamp
+ type
+ data {
+ ... on MarketLiquidationTransactionData {
+ repaidAssets
+ seizedAssets
+ liquidator
+ }
+ }
+ }
+ pageInfo {
+ countTotal
+ count
+ limit
+ skip
+ }
+ }
+ }
+
+`;
diff --git a/src/hooks/useMarketLiquidations.ts b/src/hooks/useMarketLiquidations.ts
new file mode 100644
index 00000000..e92a0100
--- /dev/null
+++ b/src/hooks/useMarketLiquidations.ts
@@ -0,0 +1,83 @@
+import { useState, useEffect, useCallback } from 'react';
+import { marketLiquidationsQuery } from '@/graphql/queries';
+import { URLS } from '@/utils/urls';
+
+export type MarketLiquidationTransaction = {
+ hash: string;
+ timestamp: number;
+ type: string;
+ data: {
+ repaidAssets: string;
+ seizedAssets: string;
+ liquidator: string;
+ };
+};
+
+/**
+ * Hook to fetch all liquidations for a specific market
+ * @param marketUniqueKey The unique key of the market
+ * @returns List of all liquidation transactions for the market
+ */
+const useMarketLiquidations = (marketUniqueKey: string | undefined) => {
+ const [liquidations, setLiquidations] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchLiquidations = useCallback(async () => {
+ if (!marketUniqueKey) {
+ setLiquidations([]);
+ return;
+ }
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ const variables = {
+ uniqueKey: marketUniqueKey,
+ };
+
+ const response = await fetch(`${URLS.MORPHO_BLUE_API}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ query: marketLiquidationsQuery,
+ variables,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch market liquidations');
+ }
+
+ const result = (await response.json()) as {
+ data: { transactions: { items: MarketLiquidationTransaction[] } };
+ };
+
+ if (result.data?.transactions?.items) {
+ setLiquidations(result.data.transactions.items);
+ } else {
+ setLiquidations([]);
+ }
+ } catch (err) {
+ console.error('Error fetching market liquidations:', err);
+ setError(err instanceof Error ? err.message : 'Unknown error');
+ } finally {
+ setLoading(false);
+ }
+ }, [marketUniqueKey]);
+
+ useEffect(() => {
+ void fetchLiquidations();
+ }, [fetchLiquidations]);
+
+ return {
+ liquidations,
+ loading,
+ error,
+ };
+};
+
+export default useMarketLiquidations;