diff --git a/app/positions/components/withdrawModal.tsx b/app/positions/components/withdrawModal.tsx
index d1c606ea..99f67d74 100644
--- a/app/positions/components/withdrawModal.tsx
+++ b/app/positions/components/withdrawModal.tsx
@@ -18,9 +18,10 @@ import { MarketPosition } from '@/utils/types';
type ModalProps = {
position: MarketPosition;
onClose: () => void;
+ refetch: () => void;
};
-export function WithdrawModal({ position, onClose }: ModalProps): JSX.Element {
+export function WithdrawModal({ position, onClose, refetch }: ModalProps): JSX.Element {
// Add state for the supply amount
const [inputError, setInputError] = useState(null);
const [withdrawAmount, setWithdrawAmount] = useState(BigInt(0));
@@ -53,6 +54,10 @@ export function WithdrawModal({ position, onClose }: ModalProps): JSX.Element {
2,
8,
)}`,
+ onSuccess: () => {
+ refetch();
+ onClose();
+ },
});
const withdraw = useCallback(async () => {
diff --git a/app/rewards/components/MarketProgram.tsx b/app/rewards/components/MarketProgram.tsx
index 83b06625..487beb61 100644
--- a/app/rewards/components/MarketProgram.tsx
+++ b/app/rewards/components/MarketProgram.tsx
@@ -6,13 +6,13 @@ import Image from 'next/image';
import { toast } from 'react-toastify';
import { Address } from 'viem';
import { useAccount, useSwitchChain } from 'wagmi';
-import { Market } from '@/hooks/useMarkets';
import { DistributionResponseType } from '@/hooks/useRewards';
import { useTransactionWithToast } from '@/hooks/useTransactionWithToast';
import { formatReadable, formatBalance } from '@/utils/balance';
import { getMarketURL } from '@/utils/external';
import { getNetworkImg } from '@/utils/networks';
import { findToken } from '@/utils/tokens';
+import { Market } from '@/utils/types';
import { MarketProgramType } from '@/utils/types';
type MarketProgramProps = {
diff --git a/src/hooks/useLiquidations.ts b/src/hooks/useLiquidations.ts
index 8b8b1e35..00920e23 100644
--- a/src/hooks/useLiquidations.ts
+++ b/src/hooks/useLiquidations.ts
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
const liquidationsQuery = `
query getLiquidations($first: Int, $skip: Int) {
@@ -68,53 +68,63 @@ type QueryResult = {
const useLiquidations = () => {
const [loading, setLoading] = useState(true);
+ const [isRefetching, setIsRefetching] = useState(false);
const [liquidatedMarketIds, setLiquidatedMarketIds] = useState>(new Set());
const [error, setError] = useState(null);
- useEffect(() => {
- const fetchLiquidations = async () => {
- try {
+ const fetchLiquidations = useCallback(async (isRefetch = false) => {
+ try {
+ if (isRefetch) {
+ setIsRefetching(true);
+ } else {
setLoading(true);
- const liquidatedIds = new Set();
- let skip = 0;
- const pageSize = 1000;
- let totalCount = 0;
+ }
+ const liquidatedIds = new Set();
+ let skip = 0;
+ const pageSize = 1000;
+ let totalCount = 0;
- do {
- const response = await fetch('https://blue-api.morpho.org/graphql', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- query: liquidationsQuery,
- variables: { first: pageSize, skip },
- }),
- });
- const result = (await response.json()) as QueryResult;
- const liquidations = result.data.transactions.items;
- const pageInfo = result.data.transactions.pageInfo;
+ do {
+ const response = await fetch('https://blue-api.morpho.org/graphql', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ query: liquidationsQuery,
+ variables: { first: pageSize, skip },
+ }),
+ });
+ const result = (await response.json()) as QueryResult;
+ const liquidations = result.data.transactions.items;
+ const pageInfo = result.data.transactions.pageInfo;
- liquidations.forEach((tx) => {
- if (tx.data && 'market' in tx.data) {
- liquidatedIds.add(tx.data.market.id);
- }
- });
+ liquidations.forEach((tx) => {
+ if (tx.data && 'market' in tx.data) {
+ liquidatedIds.add(tx.data.market.id);
+ }
+ });
- totalCount = pageInfo.countTotal;
- skip += pageInfo.count;
- } while (skip < totalCount);
+ totalCount = pageInfo.countTotal;
+ skip += pageInfo.count;
+ } while (skip < totalCount);
- setLiquidatedMarketIds(liquidatedIds);
- setLoading(false);
- } catch (_error) {
- setError(_error);
- setLoading(false);
- }
- };
+ setLiquidatedMarketIds(liquidatedIds);
+ } catch (_error) {
+ setError(_error);
+ } finally {
+ setLoading(false);
+ setIsRefetching(false);
+ }
+ }, []);
+ useEffect(() => {
fetchLiquidations().catch(console.error);
- }, []);
+ }, [fetchLiquidations]);
+
+ const refetch = useCallback(() => {
+ fetchLiquidations(true).catch(console.error);
+ }, [fetchLiquidations]);
- return { loading, liquidatedMarketIds, error };
+ return { loading, isRefetching, liquidatedMarketIds, error, refetch };
};
export default useLiquidations;
diff --git a/src/hooks/useMarkets.ts b/src/hooks/useMarkets.ts
index 96a850b6..df5b67c6 100644
--- a/src/hooks/useMarkets.ts
+++ b/src/hooks/useMarkets.ts
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
'use client';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { getRewardPer1000USD } from '@/utils/morpho';
import { isSupportedChain } from '@/utils/networks';
import { MORPHOTokenAddress } from '@/utils/tokens';
-import { OracleFeedsInfo, MarketWarning, WarningWithDetail, TokenInfo } from '@/utils/types';
+import { Market } from '@/utils/types';
import { getMarketWarningsWithDetail } from '@/utils/warnings';
import useLiquidations from './useLiquidations';
@@ -24,70 +24,6 @@ export type Reward = {
}[];
};
-export type Market = {
- id: string;
- lltv: string;
- uniqueKey: string;
- irmAddress: string;
- oracleAddress: string;
- collateralPrice: string;
- morphoBlue: {
- id: string;
- address: string;
- chain: {
- id: number;
- };
- };
- oracleInfo: {
- type: string;
- };
- oracleFeed?: OracleFeedsInfo;
- loanAsset: TokenInfo;
- collateralAsset: TokenInfo;
- state: {
- borrowAssets: string;
- supplyAssets: string;
- borrowAssetsUsd: string;
- supplyAssetsUsd: string;
- borrowShares: string;
- supplyShares: string;
- liquidityAssets: string;
- liquidityAssetsUsd: number;
- collateralAssets: string;
- collateralAssetsUsd: number | null;
- utilization: number;
- supplyApy: number;
- borrowApy: number;
- fee: number;
- timestamp: number;
- rateAtUTarget: number;
- rewards: {
- yearlySupplyTokens: string;
- asset: {
- address: string;
- priceUsd: string | null;
- spotPriceEth: string | null;
- };
- amountPerSuppliedToken: string;
- amountPerBorrowedToken: string;
- }[];
- };
- warnings: MarketWarning[];
- badDebt?: {
- underlying: number;
- usd: number;
- };
- realizedBadDebt?: {
- underlying: number;
- usd: number;
- };
-
- // appended by us
- rewardPer1000USD?: string;
- warningsWithDetail: WarningWithDetail[];
- isProtectedByLiquidationBots: boolean;
-};
-
const marketsQuery = `
query getMarkets($first: Int, $where: MarketFilters) {
markets(first: $first, where: $where) {
@@ -202,18 +138,24 @@ const marketsQuery = `
const useMarkets = () => {
const [loading, setLoading] = useState(true);
+ const [isRefetching, setIsRefetching] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const {
loading: liquidationsLoading,
liquidatedMarketIds,
error: liquidationsError,
+ refetch: refetchLiquidations,
} = useLiquidations();
- useEffect(() => {
- const fetchData = async () => {
+ const fetchData = useCallback(
+ async (isRefetch = false) => {
try {
- setLoading(true);
+ if (isRefetch) {
+ setIsRefetching(true);
+ } else {
+ setLoading(true);
+ }
// Fetch markets
const marketsResponse = await fetch('https://blue-api.morpho.org/graphql', {
@@ -263,22 +205,34 @@ const useMarkets = () => {
});
setData(final);
- setLoading(false);
} catch (_error) {
setError(_error);
+ } finally {
setLoading(false);
+ setIsRefetching(false);
}
- };
+ },
+ [liquidatedMarketIds],
+ );
+ useEffect(() => {
if (!liquidationsLoading) {
fetchData().catch(console.error);
}
- }, [liquidationsLoading, liquidatedMarketIds]);
+ }, [liquidationsLoading, fetchData]);
+
+ const refetch = useCallback(
+ (onSuccess?: () => void) => {
+ refetchLiquidations();
+ fetchData(true).then(onSuccess).catch(console.error);
+ },
+ [refetchLiquidations, fetchData],
+ );
const isLoading = loading || liquidationsLoading;
const combinedError = error || liquidationsError;
- return { loading: isLoading, data, error: combinedError };
+ return { loading: isLoading, isRefetching, data, error: combinedError, refetch };
};
export default useMarkets;
diff --git a/src/hooks/useRebalance.ts b/src/hooks/useRebalance.ts
index 713e0223..472d8249 100644
--- a/src/hooks/useRebalance.ts
+++ b/src/hooks/useRebalance.ts
@@ -9,7 +9,7 @@ import { getBundlerV2, MORPHO } from '@/utils/morpho';
import { GroupedPosition, RebalanceAction } from '@/utils/types';
import { usePermit2 } from './usePermit2';
-export const useRebalance = (groupedPosition: GroupedPosition) => {
+export const useRebalance = (groupedPosition: GroupedPosition, onRebalance?: () => void) => {
const [rebalanceActions, setRebalanceActions] = useState([]);
const [isConfirming, setIsConfirming] = useState(false);
const [currentStep, setCurrentStep] = useState<
@@ -65,6 +65,7 @@ export const useRebalance = (groupedPosition: GroupedPosition) => {
successText: 'Positions rebalanced successfully',
errorText: 'Failed to rebalance positions',
chainId: groupedPosition.chainId,
+ onSuccess: onRebalance,
});
const executeRebalance = useCallback(async () => {
diff --git a/src/hooks/useTransactionWithToast.tsx b/src/hooks/useTransactionWithToast.tsx
index e9e3fc7b..2b66fa03 100644
--- a/src/hooks/useTransactionWithToast.tsx
+++ b/src/hooks/useTransactionWithToast.tsx
@@ -14,6 +14,7 @@ type UseTransactionWithToastProps = {
chainId?: number;
pendingDescription?: string;
successDescription?: string;
+ onSuccess?: () => void;
};
export function useTransactionWithToast({
@@ -24,6 +25,7 @@ export function useTransactionWithToast({
chainId,
pendingDescription,
successDescription,
+ onSuccess,
}: UseTransactionWithToastProps) {
const {
data: hash,
@@ -72,6 +74,9 @@ export function useTransactionWithToast({
autoClose: 5000,
onClick,
});
+ if (onSuccess) {
+ onSuccess();
+ }
}
if (txError) {
toast.update(toastId, {
@@ -96,6 +101,7 @@ export function useTransactionWithToast({
toastId,
onClick,
renderToastContent,
+ onSuccess,
]);
return { sendTransactionAsync, sendTransaction, isConfirming, isConfirmed };
diff --git a/src/hooks/useUserPositions.ts b/src/hooks/useUserPositions.ts
index b3164dd0..54e63742 100644
--- a/src/hooks/useUserPositions.ts
+++ b/src/hooks/useUserPositions.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { SupportedNetworks } from '@/utils/networks';
import { MarketPosition, UserTransaction } from '@/utils/types';
@@ -123,14 +123,22 @@ const query = `query getUserMarketPositions(
const useUserPositions = (user: string | undefined) => {
const [loading, setLoading] = useState(true);
+ const [isRefetching, setIsRefetching] = useState(false);
const [data, setData] = useState([]);
const [history, setHistory] = useState([]);
const [error, setError] = useState(null);
- useEffect(() => {
- const fetchData = async () => {
+ const fetchData = useCallback(
+ async (isRefetch = false) => {
+ if (!user) return;
+
try {
- setLoading(true);
+ if (isRefetch) {
+ setIsRefetching(true);
+ } else {
+ setLoading(true);
+ }
+
const [responseMainnet, responseBase] = await Promise.all([
fetch('https://blue-api.morpho.org/graphql', {
method: 'POST',
@@ -187,19 +195,28 @@ const useUserPositions = (user: string | undefined) => {
setHistory(transactions);
setData(filtered);
- setLoading(false);
} catch (_error) {
setError(_error);
+ } finally {
setLoading(false);
+ setIsRefetching(false);
}
- };
-
- if (!user) return;
+ },
+ [user],
+ );
+ useEffect(() => {
fetchData().catch(console.error);
- }, [user]);
+ }, [fetchData]);
+
+ const refetch = useCallback(
+ (onSuccess?: () => void) => {
+ fetchData(true).then(onSuccess).catch(console.error);
+ },
+ [fetchData],
+ );
- return { loading, data, history, error };
+ return { loading, isRefetching, data, history, error, refetch };
};
export default useUserPositions;
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 487a3384..86755df5 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -239,3 +239,68 @@ export type GroupedPosition = {
percentage: number;
}[];
};
+
+// Add this type to the existing types in the file
+export type Market = {
+ id: string;
+ lltv: string;
+ uniqueKey: string;
+ irmAddress: string;
+ oracleAddress: string;
+ collateralPrice: string;
+ morphoBlue: {
+ id: string;
+ address: string;
+ chain: {
+ id: number;
+ };
+ };
+ oracleInfo: {
+ type: string;
+ };
+ oracleFeed?: OracleFeedsInfo;
+ loanAsset: TokenInfo;
+ collateralAsset: TokenInfo;
+ state: {
+ borrowAssets: string;
+ supplyAssets: string;
+ borrowAssetsUsd: string;
+ supplyAssetsUsd: string;
+ borrowShares: string;
+ supplyShares: string;
+ liquidityAssets: string;
+ liquidityAssetsUsd: number;
+ collateralAssets: string;
+ collateralAssetsUsd: number | null;
+ utilization: number;
+ supplyApy: number;
+ borrowApy: number;
+ fee: number;
+ timestamp: number;
+ rateAtUTarget: number;
+ rewards: {
+ yearlySupplyTokens: string;
+ asset: {
+ address: string;
+ priceUsd: string | null;
+ spotPriceEth: string | null;
+ };
+ amountPerSuppliedToken: string;
+ amountPerBorrowedToken: string;
+ }[];
+ };
+ warnings: MarketWarning[];
+ badDebt?: {
+ underlying: number;
+ usd: number;
+ };
+ realizedBadDebt?: {
+ underlying: number;
+ usd: number;
+ };
+
+ // appended by us
+ rewardPer1000USD?: string;
+ warningsWithDetail: WarningWithDetail[];
+ isProtectedByLiquidationBots: boolean;
+};
diff --git a/src/utils/warnings.ts b/src/utils/warnings.ts
index e4939dad..88765638 100644
--- a/src/utils/warnings.ts
+++ b/src/utils/warnings.ts
@@ -1,4 +1,4 @@
-import { Market } from '@/hooks/useMarkets';
+import { Market } from '@/utils/types';
import { WarningCategory, WarningWithDetail } from './types';
const morphoOfficialWarnings: WarningWithDetail[] = [