Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/features/market-detail/components/market-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ChevronDownIcon } from '@radix-ui/react-icons';
import { GrStatusGood } from 'react-icons/gr';
import { IoWarningOutline, IoEllipsisVertical } from 'react-icons/io5';
import { MdError } from 'react-icons/md';
import { BsArrowUpCircle, BsArrowDownLeftCircle } from 'react-icons/bs';
import { BsArrowUpCircle, BsArrowDownLeftCircle, BsFillLightningFill } from 'react-icons/bs';
import { FiExternalLink } from 'react-icons/fi';
import { LuCopy } from 'react-icons/lu';
import { Button } from '@/components/ui/button';
Expand Down Expand Up @@ -121,6 +121,7 @@ type MarketHeaderProps = {
allWarnings: WarningWithDetail[];
onSupplyClick: () => void;
onBorrowClick: () => void;
accrueInterest: () => void;
};

export function MarketHeader({
Expand All @@ -132,6 +133,7 @@ export function MarketHeader({
allWarnings,
onSupplyClick,
onBorrowClick,
accrueInterest,
}: MarketHeaderProps) {
const [isExpanded, setIsExpanded] = useState(false);
const { short: rateLabel } = useRateLabel();
Expand Down Expand Up @@ -351,6 +353,12 @@ export function MarketHeader({
>
Borrow
</DropdownMenuItem>
<DropdownMenuItem
onClick={accrueInterest}
startContent={<BsFillLightningFill className="h-4 w-4" />}
>
Accrue Interest
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => window.open(getMarketURL(marketId, network), '_blank')}
startContent={<FiExternalLink className="h-4 w-4" />}
Expand Down
85 changes: 64 additions & 21 deletions src/features/market-detail/market-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

import { useState, useCallback, useMemo } from 'react';
import { useParams } from 'next/navigation';
import { parseUnits, formatUnits } from 'viem';
import { useConnection } from 'wagmi';
import { parseUnits, formatUnits, type Address, encodeFunctionData } from 'viem';
import { useConnection, useSwitchChain } from 'wagmi';
import morphoAbi from '@/abis/morpho';
import { BorrowModal } from '@/modals/borrow/borrow-modal';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Spinner } from '@/components/ui/spinner';
Expand All @@ -16,6 +17,7 @@ import { useOraclePrice } from '@/hooks/useOraclePrice';
import { useTransactionFilters } from '@/stores/useTransactionFilters';
import { useMarketDetailPreferences, type MarketDetailTab } from '@/stores/useMarketDetailPreferences';
import useUserPosition from '@/hooks/useUserPosition';
import { useTransactionWithToast } from '@/hooks/useTransactionWithToast';
import type { SupportedNetworks } from '@/utils/networks';
import { BorrowersTable } from '@/features/market-detail/components/borrowers-table';
import { BorrowsTable } from '@/features/market-detail/components/borrows-table';
Expand Down Expand Up @@ -96,6 +98,22 @@ function MarketContent() {
totalCount: suppliersTotalCount,
} = useAllMarketSuppliers(market?.uniqueKey, network);

const { mutateAsync: switchChainAsync } = useSwitchChain();

// Transaction hook for accruing interest
const { sendTransaction } = useTransactionWithToast({
toastId: 'accrue-interest',
pendingText: 'Accruing Interest',
successText: 'Interest Accrued',
errorText: 'Failed to accrue interest',
chainId: market?.morphoBlue.chain.id,
pendingDescription: 'Updating market interest rates...',
successDescription: 'Market interest rates have been updated',
onSuccess: () => {
void refetchMarket();
},
});

// 6. All memoized values and callbacks

// Helper to scale user input to token amount
Expand Down Expand Up @@ -247,6 +265,30 @@ function MarketContent() {
setShowBorrowModal(true);
};

const handleAccrueInterest = async () => {
await switchChainAsync({ chainId: market.morphoBlue.chain.id });
const morphoAddress = market.morphoBlue.address as Address;

sendTransaction({
to: morphoAddress,
account: address,
data: encodeFunctionData({
abi: morphoAbi,
functionName: 'accrueInterest',
args: [
{
loanToken: market.loanAsset.address as Address,
collateralToken: market.collateralAsset.address as Address,
oracle: market.oracleAddress as Address,
irm: market.irmAddress as Address,
lltv: BigInt(market.lltv),
},
],
}),
chainId: market.morphoBlue.chain.id,
});
};

return (
<>
<Header />
Expand All @@ -261,6 +303,7 @@ function MarketContent() {
allWarnings={allWarnings}
onSupplyClick={handleSupplyClick}
onBorrowClick={handleBorrowClick}
accrueInterest={handleAccrueInterest}
/>

{showBorrowModal && (
Expand Down Expand Up @@ -358,6 +401,25 @@ function MarketContent() {
</TabsContent>

<TabsContent value="positions">
{/* Tables */}
<div className="mt-6">
<SuppliersTable
chainId={network}
market={market}
minShares={scaledMinSupplierShares}
onOpenFiltersModal={() => setShowSupplierFiltersModal(true)}
/>
</div>
<div className="mt-6">
<BorrowersTable
chainId={network}
market={market}
minShares={scaledMinBorrowerShares}
oraclePrice={oraclePrice}
onOpenFiltersModal={() => setShowBorrowerFiltersModal(true)}
/>
</div>

{/* Suppliers row: Pie + Concentration */}
<div className="grid gap-6 lg:grid-cols-2">
<SuppliersPieChart
Expand Down Expand Up @@ -397,25 +459,6 @@ function MarketContent() {
oraclePrice={oraclePrice}
/>
</div>

{/* Tables */}
<div className="mt-6">
<SuppliersTable
chainId={network}
market={market}
minShares={scaledMinSupplierShares}
onOpenFiltersModal={() => setShowSupplierFiltersModal(true)}
/>
</div>
<div className="mt-6">
<BorrowersTable
chainId={network}
market={market}
minShares={scaledMinBorrowerShares}
oraclePrice={oraclePrice}
onOpenFiltersModal={() => setShowBorrowerFiltersModal(true)}
/>
</div>
</TabsContent>
</Tabs>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr
className="cursor-pointer hover:bg-gray-50"
onClick={() => toggleRow(rowKey)}
>
{/* Chain image */}
<TableCell className="w-10">
<div className="flex items-center justify-center">
<Image
Expand All @@ -238,6 +239,8 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr
/>
</div>
</TableCell>

{/* Loan asset details */}
<TableCell data-label="Size">
<div className="flex items-center justify-center gap-2">
<span className="font-medium">{formatReadable(groupedPosition.totalSupply)}</span>
Expand All @@ -251,11 +254,15 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr
/>
</div>
</TableCell>

{/* Current APR/APY */}
<TableCell data-label={`${rateLabel} (now)`}>
<div className="flex items-center justify-center">
<span className="font-medium">{formatReadable((isAprDisplay ? convertApyToApr(avgApy) : avgApy) * 100)}%</span>
</div>
</TableCell>

{/* Accrued interest */}
<TableCell data-label={`Interest Accrued (${period})`}>
<div className="flex items-center justify-center gap-2">
{isEarningsLoading ? (
Expand Down Expand Up @@ -293,6 +300,8 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr
)}
</div>
</TableCell>

{/* Collateral exposure */}
<TableCell data-label="Collateral">
<CollateralIconsDisplay
collaterals={groupedPosition.collaterals}
Expand All @@ -301,6 +310,8 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr
iconSize={20}
/>
</TableCell>

{/* Risk indicators */}
<TableCell
data-label="Risk Tiers"
className="align-middle"
Expand All @@ -309,6 +320,8 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr
<AggregatedRiskIndicators groupedPosition={groupedPosition} />
</div>
</TableCell>

{/* Actions button */}
<TableCell
data-label="Actions"
className="justify-center px-4 py-3"
Expand Down