diff --git a/src/features/markets/components/table/market-table-body.tsx b/src/features/markets/components/table/market-table-body.tsx index 8d79e8fa..7cd86017 100644 --- a/src/features/markets/components/table/market-table-body.tsx +++ b/src/features/markets/components/table/market-table-body.tsx @@ -241,9 +241,7 @@ export function MarketTableBody({ currentEntries, expandedRowId, setExpandedRowI className="z-50 text-center" style={{ minWidth: '85px', paddingLeft: 3, paddingRight: 3 }} > -

- {item.state.dailySupplyApy != null ? : '—'} -

+

{item.state.dailySupplyApy != null ? : '—'}

)} {columnVisibility.dailyBorrowAPY && ( @@ -252,9 +250,7 @@ export function MarketTableBody({ currentEntries, expandedRowId, setExpandedRowI className="z-50 text-center" style={{ minWidth: '85px', paddingLeft: 3, paddingRight: 3 }} > -

- {item.state.dailyBorrowApy != null ? : '—'} -

+

{item.state.dailyBorrowApy != null ? : '—'}

)} {columnVisibility.weeklySupplyAPY && ( diff --git a/src/features/positions/components/supplied-markets-detail.tsx b/src/features/positions/components/supplied-markets-detail.tsx index d59b35d9..9c370d9b 100644 --- a/src/features/positions/components/supplied-markets-detail.tsx +++ b/src/features/positions/components/supplied-markets-detail.tsx @@ -8,14 +8,18 @@ import { MarketRiskIndicators } from '@/features/markets/components/market-risk- import { APYCell } from '@/features/markets/components/apy-breakdown-tooltip'; import { useRateLabel } from '@/hooks/useRateLabel'; import { useModal } from '@/hooks/useModal'; -import { formatReadable, formatBalance } from '@/utils/balance'; +import { formatBalance } from '@/utils/balance'; import type { MarketPosition, GroupedPosition } from '@/utils/types'; -import { getCollateralColorFromPalette, OTHER_COLOR } from '@/features/positions/utils/colors'; -import { useChartColors } from '@/constants/chartColors'; import { AllocationCell } from './allocation-cell'; +import { UserPositionsChart } from './user-positions-chart'; +import type { UserTransaction } from '@/utils/types'; +import type { PositionSnapshot } from '@/utils/positions'; + type SuppliedMarketsDetailProps = { groupedPosition: GroupedPosition; - showCollateralExposure: boolean; + transactions: UserTransaction[]; + snapshotsByChain: Record>; + chainBlockData: Record; }; function MarketRow({ position, totalSupply, rateLabel }: { position: MarketPosition; totalSupply: number; rateLabel: string }) { @@ -106,9 +110,8 @@ function MarketRow({ position, totalSupply, rateLabel }: { position: MarketPosit } // shared similar style with @vault-allocation-detail.tsx -export function SuppliedMarketsDetail({ groupedPosition, showCollateralExposure }: SuppliedMarketsDetailProps) { +export function SuppliedMarketsDetail({ groupedPosition, transactions, snapshotsByChain, chainBlockData }: SuppliedMarketsDetailProps) { const { short: rateLabel } = useRateLabel(); - const { pie: pieColors } = useChartColors(); // Sort markets by size const sortedMarkets = [...groupedPosition.markets].sort( @@ -128,45 +131,14 @@ export function SuppliedMarketsDetail({ groupedPosition, showCollateralExposure className="overflow-hidden" >
- {/* Conditionally render collateral exposure section */} - {showCollateralExposure && ( -
-
-

Collateral Exposure

-
- {groupedPosition.processedCollaterals.map((collateral, colIndex) => ( -
- ))} -
-
- {groupedPosition.processedCollaterals.map((collateral, colIndex) => ( - - - ■ - {' '} - {collateral.symbol}: {formatReadable(collateral.percentage)}% - - ))} -
-
-
- )} + {/* Position History Chart with synchronized pie */} + {/* Markets Table - Always visible */} diff --git a/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx b/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx index 252599b0..71107ceb 100644 --- a/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx +++ b/src/features/positions/components/supplied-morpho-blue-grouped-table.tsx @@ -109,10 +109,12 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr isRefetching, isEarningsLoading, actualBlockData, + transactions, + snapshotsByChain, } = useUserPositionsSummaryData(account, period); const [expandedRows, setExpandedRows] = useState>(new Set()); - const { showCollateralExposure, setShowCollateralExposure, showEarningsInUsd, setShowEarningsInUsd } = usePositionsPreferences(); + const { showEarningsInUsd, setShowEarningsInUsd } = usePositionsPreferences(); const { isOpen: isSettingsOpen, onOpen: onSettingsOpen, onOpenChange: onSettingsOpenChange } = useDisclosure(); const { address } = useConnection(); const { isAprDisplay } = useAppSettings(); @@ -389,7 +391,9 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr > @@ -451,16 +455,6 @@ export function SuppliedMorphoBlueGroupedTable({ account }: SuppliedMorphoBlueGr title="Display Options" helper="Customize what information is shown in the table" > - - - >; + chainBlockData: Record; +}; + +// Props for standalone usage (history page) +type StandaloneChartProps = BaseChartProps & { + variant: 'standalone'; + chainId: SupportedNetworks; + loanAssetAddress: Address; + loanAssetSymbol: string; + loanAssetDecimals: number; + startTimestamp: number; + endTimestamp: number; + markets: { + uniqueKey: string; + collateralSymbol: string; + collateralAddress: string; + currentSupplyAssets: string; + }[]; + transactions: UserTransaction[]; + snapshots: Map | undefined; +}; + +export type UserPositionsChartProps = GroupedPositionChartProps | StandaloneChartProps; + +// Pie chart data type +type PieDataPoint = { + key: string; // unique market key + name: string; + value: number; + color: string; + percentage: number; +}; + +function ChartContent({ + dataPoints, + markets, + loanAssetSymbol, + height, +}: { + dataPoints: PositionHistoryDataPoint[]; + markets: MarketInfo[]; + loanAssetSymbol: string; + height: number; +}) { + const chartColors = useChartColors(); + + // Track which data point is being hovered (for synced pie chart) + const [hoveredIndex, setHoveredIndex] = useState(null); + + // Build a map of market uniqueKey -> index for consistent color assignment + // Note: markets from hook already have lowercase uniqueKeys + const marketColorMap = useMemo(() => { + const map: Record = {}; + markets.forEach((market, index) => { + map[market.uniqueKey] = index; + }); + return map; + }, [markets]); + + // Get color for a market based on its index in the markets array + const getMarketColor = useCallback( + (marketKey: string): string => { + const index = marketColorMap[marketKey] ?? 0; + return chartColors.pie[index % chartColors.pie.length]; + }, + [marketColorMap, chartColors.pie], + ); + + // Detect duplicate collateral symbols and create display names + const marketDisplayNames = useMemo(() => { + const symbolCounts: Record = {}; + + // First pass: count occurrences + markets.forEach((market) => { + symbolCounts[market.collateralSymbol] = (symbolCounts[market.collateralSymbol] || 0) + 1; + }); + + // Second pass: create display names + const names: Record = {}; + markets.forEach((market) => { + if (symbolCounts[market.collateralSymbol] > 1) { + // Duplicate symbol - add market key prefix (first 8 chars) + const keyPrefix = market.uniqueKey.slice(0, 8); + names[market.uniqueKey] = `${market.collateralSymbol} (${keyPrefix}...)`; + } else { + names[market.uniqueKey] = market.collateralSymbol; + } + }); + + return names; + }, [markets]); + + // Current data point for pie chart (hovered or latest) + const currentDataPoint = useMemo(() => { + if (hoveredIndex !== null && dataPoints[hoveredIndex]) { + return dataPoints[hoveredIndex]; + } + return dataPoints.at(-1); + }, [hoveredIndex, dataPoints]); + + // Pie chart data derived from current data point + // Keep all markets for consistent height, sort by value descending + const pieData = useMemo((): PieDataPoint[] => { + if (!currentDataPoint) return []; + + const total = currentDataPoint.total || 0; + return markets + .map((market) => { + const value = Number(currentDataPoint[market.uniqueKey] ?? 0); + return { + key: market.uniqueKey, + name: marketDisplayNames[market.uniqueKey] || market.collateralSymbol, + value, + color: getMarketColor(market.uniqueKey), + percentage: total > 0 && value > 0 ? (value / total) * 100 : 0, + }; + }) + .sort((a, b) => b.value - a.value); + }, [currentDataPoint, markets, getMarketColor, marketDisplayNames]); + + // Format date for display + const formatDate = (timestamp: number) => { + return new Date(timestamp * 1000).toLocaleDateString(undefined, { + month: 'short', + day: 'numeric', + }); + }; + + // Don't render if no meaningful data + if (dataPoints.length <= 1 || markets.length === 0) { + return null; + } + + const currentTimestamp = currentDataPoint?.timestamp; + const isHistorical = hoveredIndex !== null && hoveredIndex < dataPoints.length - 1; + + return ( + + {/* Responsive: stack vertically on mobile, side-by-side on larger screens */} +
+ {/* Stacked Area Chart - Left side */} +
+
+

Position History

+
+
+ + { + if (state?.activeTooltipIndex !== undefined) { + setHoveredIndex(state.activeTooltipIndex); + } + }} + onMouseLeave={() => setHoveredIndex(null)} + > + + + new Date(time * 1000).toLocaleDateString(undefined, { + month: 'short', + day: 'numeric', + }) + } + tick={{ fontSize: 10, fill: 'var(--color-text-secondary)' }} + /> + formatReadable(v)} + tick={{ fontSize: 10, fill: 'var(--color-text-secondary)' }} + width={55} + domain={[0, 'auto']} + /> + { + if (!active || !payload || payload.length === 0) return null; + + const dataPoint = dataPoints.find((dp) => dp.timestamp === Number(label)); + const total = dataPoint?.total ?? 0; + + return ( +
+

+ {new Date((label ?? 0) * 1000).toLocaleString(undefined, { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} +

+
+ {/* Sort by value descending, show only non-zero */} + {pieData + .filter((entry) => entry.value > 0) + .map((entry) => ( +
+
+ + {entry.name} +
+ + {formatReadable(entry.value)} ({entry.percentage.toFixed(1)}%) + +
+ ))} +
+
+ Total + + {formatReadable(total)} {loanAssetSymbol} + +
+
+ ); + }} + /> + {markets.map((market) => { + const key = market.uniqueKey; + const color = getMarketColor(key); + const displayName = marketDisplayNames[key] || market.collateralSymbol; + return ( + + ); + })} +
+
+
+
+ + {/* Synchronized Pie Chart - Right side on desktop, below on mobile */} +
+ {/* Mobile: horizontal layout with pie on left, legend on right */} + {/* Desktop: vertical layout */} +
+
+

{isHistorical ? 'Historical' : 'Current'}

+ {currentTimestamp &&

{formatDate(currentTimestamp)}

} +
+ + + + {pieData.map((entry) => ( + + ))} + + + +
+ {/* Legend - show all markets, dim zero values */} +
+
+ {pieData.slice(0, 5).map((entry) => { + const isZero = entry.value === 0; + return ( +
+
+ + {entry.name} +
+ {isZero ? '—' : `${entry.percentage.toFixed(1)}%`} +
+ ); + })} + {pieData.length > 5 &&

+{pieData.length - 5} more

} +
+
+
+
+
+ ); +} + +// Extract params from props to normalize between variants +function useChartParams(props: UserPositionsChartProps) { + return useMemo(() => { + if (props.variant === 'grouped') { + const { groupedPosition, transactions, snapshotsByChain, chainBlockData } = props; + const chainId = groupedPosition.chainId; + const chainSnapshots = snapshotsByChain[chainId]; + const blockData = chainBlockData[chainId]; + + if (!blockData) { + return null; + } + + const startTimestamp = blockData.timestamp; + const endTimestamp = Math.floor(Date.now() / 1000); + + const markets = groupedPosition.markets.map((position) => ({ + uniqueKey: position.market.uniqueKey, + collateralSymbol: position.market.collateralAsset?.symbol ?? 'Unknown', + collateralAddress: position.market.collateralAsset?.address ?? '', + currentSupplyAssets: position.state.supplyAssets, + })); + + return { + markets, + loanAssetDecimals: groupedPosition.loanAssetDecimals, + loanAssetSymbol: groupedPosition.loanAssetSymbol, + chainId: chainId as SupportedNetworks, + startTimestamp, + endTimestamp, + transactions, + snapshots: chainSnapshots, + }; + } + + // Standalone variant + return { + markets: props.markets, + loanAssetDecimals: props.loanAssetDecimals, + loanAssetSymbol: props.loanAssetSymbol, + chainId: props.chainId, + startTimestamp: props.startTimestamp, + endTimestamp: props.endTimestamp, + transactions: props.transactions, + snapshots: props.snapshots, + }; + }, [props]); +} + +export function UserPositionsChart(props: UserPositionsChartProps) { + const height = props.height ?? 180; + const debug = props.debug ?? false; + + const chartParams = useChartParams(props); + + const { dataPoints, markets: marketInfoList } = usePositionHistoryChart({ + markets: chartParams?.markets ?? [], + loanAssetDecimals: chartParams?.loanAssetDecimals ?? 18, + chainId: chartParams?.chainId ?? (1 as SupportedNetworks), + startTimestamp: chartParams?.startTimestamp ?? 0, + endTimestamp: chartParams?.endTimestamp ?? 0, + transactions: chartParams?.transactions ?? [], + snapshots: chartParams?.snapshots, + debug, + }); + + if (!chartParams) { + return null; + } + + return ( + + ); +} diff --git a/src/hooks/usePositionHistoryChart.ts b/src/hooks/usePositionHistoryChart.ts new file mode 100644 index 00000000..f9d6434c --- /dev/null +++ b/src/hooks/usePositionHistoryChart.ts @@ -0,0 +1,308 @@ +import { useMemo } from 'react'; +import type { UserTransaction } from '@/utils/types'; +import type { PositionSnapshot } from '@/utils/positions'; +import { UserTxTypes } from '@/utils/types'; +import { formatBalance } from '@/utils/balance'; +import type { SupportedNetworks } from '@/utils/networks'; + +// Maximum number of data points before batching kicks in +const MAX_DATA_POINTS = 50; + +export type PositionHistoryDataPoint = { + timestamp: number; + total: number; + eventType?: 'supply' | 'withdraw' | 'batch'; + eventAmount?: number; + eventMarketKey?: string; + batchCount?: number; // Number of events in this batch + [marketKey: string]: number | string | undefined; +}; + +export type MarketInfo = { + uniqueKey: string; + collateralSymbol: string; + collateralAddress: string; +}; + +export type PositionHistoryDebugInfo = { + inputTransactionsCount: number; + relevantTransactionsCount: number; + filteredByMarket: number; + filteredByType: number; + filteredByTime: number; + dataPointsCount: number; + startTimestamp: number; + endTimestamp: number; + marketsCount: number; + snapshotFound: boolean; + batchingApplied: boolean; + batchWindowSeconds: number; +}; + +type UsePositionHistoryChartOptions = { + // Market data + markets: { + uniqueKey: string; + collateralSymbol: string; + collateralAddress: string; + currentSupplyAssets: string; + }[]; + loanAssetDecimals: number; + chainId: SupportedNetworks; + + // Time range + startTimestamp: number; + endTimestamp: number; + + // Data inputs + transactions: UserTransaction[]; + snapshots: Map | undefined; + + // Debug + debug?: boolean; +}; + +export type PositionHistoryChartData = { + dataPoints: PositionHistoryDataPoint[]; + markets: MarketInfo[]; + debugInfo: PositionHistoryDebugInfo | null; +}; + +export function usePositionHistoryChart({ + markets, + loanAssetDecimals, + chainId, + startTimestamp, + endTimestamp, + transactions, + snapshots, + debug = false, +}: UsePositionHistoryChartOptions): PositionHistoryChartData { + return useMemo(() => { + const decimals = loanAssetDecimals; + + // Build market info list - use lowercase keys consistently + const marketInfoList: MarketInfo[] = markets.map((m) => ({ + uniqueKey: m.uniqueKey.toLowerCase(), + collateralSymbol: m.collateralSymbol, + collateralAddress: m.collateralAddress, + })); + + const marketKeys = marketInfoList.map((m) => m.uniqueKey); + + // Debug counters + let filteredByMarket = 0; + let filteredByType = 0; + let filteredByTime = 0; + + // Filter transactions step by step for debugging + const txsWithMarket = transactions.filter((tx) => { + const txMarketKey = tx.data?.market?.uniqueKey?.toLowerCase(); + const matches = txMarketKey && marketKeys.includes(txMarketKey); + if (!matches) filteredByMarket++; + return matches; + }); + + const txsWithCorrectType = txsWithMarket.filter((tx) => { + const isSupplyOrWithdraw = tx.type === UserTxTypes.MarketSupply || tx.type === UserTxTypes.MarketWithdraw; + if (!isSupplyOrWithdraw) filteredByType++; + return isSupplyOrWithdraw; + }); + + const txsInTimeRange = txsWithCorrectType.filter((tx) => { + const inRange = Number(tx.timestamp) >= startTimestamp && Number(tx.timestamp) <= endTimestamp; + if (!inRange) filteredByTime++; + return inRange; + }); + + const relevantTxs = txsInTimeRange.sort((a, b) => Number(a.timestamp) - Number(b.timestamp)); + + // Initialize positions from "before" snapshot - use lowercase keys + const marketPositions: Record = {}; + let snapshotFound = false; + + for (const market of markets) { + const key = market.uniqueKey.toLowerCase(); + const snapshot = snapshots?.get(key); + if (snapshot) snapshotFound = true; + marketPositions[key] = snapshot ? BigInt(snapshot.supplyAssets) : 0n; + } + + // Helper to calculate total and create data point + const createDataPoint = ( + timestamp: number, + eventType?: 'supply' | 'withdraw' | 'batch', + eventAmount?: number, + eventMarketKey?: string, + batchCount?: number, + ): PositionHistoryDataPoint => { + const point: PositionHistoryDataPoint = { timestamp, total: 0 }; + if (eventType) point.eventType = eventType; + if (eventAmount !== undefined) point.eventAmount = eventAmount; + if (eventMarketKey) point.eventMarketKey = eventMarketKey; + if (batchCount !== undefined) point.batchCount = batchCount; + + let total = 0; + for (const market of marketInfoList) { + const positionBigInt = marketPositions[market.uniqueKey] ?? 0n; + const value = Number(formatBalance(positionBigInt, decimals)); + point[market.uniqueKey] = value; + total += value; + } + point.total = total; + return point; + }; + + // Group transactions by timestamp (same block = same timestamp) + const txsByTimestamp = new Map(); + for (const tx of relevantTxs) { + const ts = Number(tx.timestamp); + if (!txsByTimestamp.has(ts)) { + txsByTimestamp.set(ts, []); + } + txsByTimestamp.get(ts)!.push(tx); + } + + const uniqueTimestamps = Array.from(txsByTimestamp.keys()).sort((a, b) => a - b); + + // Determine if we need batching (only when unique timestamps exceed MAX_DATA_POINTS) + const needsBatching = uniqueTimestamps.length > MAX_DATA_POINTS; + const timeRange = endTimestamp - startTimestamp; + // If batching needed, calculate window size to get ~MAX_DATA_POINTS buckets + const batchWindowSeconds = needsBatching ? Math.ceil(timeRange / MAX_DATA_POINTS) : 0; + + const dataPoints: PositionHistoryDataPoint[] = []; + + // Add starting data point + dataPoints.push(createDataPoint(startTimestamp)); + + // Helper to apply transaction to positions + const applyTransaction = (tx: UserTransaction) => { + const key = tx.data.market.uniqueKey.toLowerCase(); + const assets = BigInt(tx.data?.assets ?? '0'); + + if (tx.type === UserTxTypes.MarketSupply) { + marketPositions[key] = (marketPositions[key] ?? 0n) + assets; + } else if (tx.type === UserTxTypes.MarketWithdraw) { + const newValue = (marketPositions[key] ?? 0n) - assets; + // Never allow negative positions - clamp to 0 + marketPositions[key] = newValue < 0n ? 0n : newValue; + } + }; + + if (needsBatching) { + // Batch timestamps into time windows + let currentWindowEnd = startTimestamp + batchWindowSeconds; + let batchTxs: UserTransaction[] = []; + let batchTimestamp = 0; + + const processBatch = () => { + if (batchTxs.length === 0) return; + + // Apply all transactions in the batch to update positions + let totalSupplied = 0n; + let totalWithdrawn = 0n; + + for (const tx of batchTxs) { + applyTransaction(tx); + const assets = BigInt(tx.data?.assets ?? '0'); + if (tx.type === UserTxTypes.MarketSupply) { + totalSupplied += assets; + } else { + totalWithdrawn += assets; + } + } + + // Create a single data point for the batch at the last timestamp in the batch + const netChange = totalSupplied - totalWithdrawn; + const eventAmount = Number(formatBalance(netChange >= 0n ? netChange : -netChange, decimals)); + + dataPoints.push(createDataPoint(batchTimestamp, 'batch', eventAmount, undefined, batchTxs.length)); + }; + + for (const ts of uniqueTimestamps) { + // Move to the correct window + while (ts >= currentWindowEnd) { + processBatch(); + batchTxs = []; + currentWindowEnd += batchWindowSeconds; + } + + const txsAtTimestamp = txsByTimestamp.get(ts) ?? []; + batchTxs.push(...txsAtTimestamp); + batchTimestamp = ts; // Use the last timestamp in the batch + } + + // Process remaining batch + processBatch(); + } else { + // Process each timestamp group as one data point + for (const ts of uniqueTimestamps) { + const txsAtTimestamp = txsByTimestamp.get(ts) ?? []; + + // Track event info for single-tx timestamps + let totalSupplied = 0n; + let totalWithdrawn = 0n; + let lastMarketKey: string | undefined; + + for (const tx of txsAtTimestamp) { + applyTransaction(tx); + const assets = BigInt(tx.data?.assets ?? '0'); + lastMarketKey = tx.data.market.uniqueKey.toLowerCase(); + if (tx.type === UserTxTypes.MarketSupply) { + totalSupplied += assets; + } else { + totalWithdrawn += assets; + } + } + + // Determine event type and amount + if (txsAtTimestamp.length === 1) { + const tx = txsAtTimestamp[0]; + const eventType = tx.type === UserTxTypes.MarketSupply ? 'supply' : 'withdraw'; + const eventAmount = Number(formatBalance(BigInt(tx.data?.assets ?? '0'), decimals)); + dataPoints.push(createDataPoint(ts, eventType, eventAmount, lastMarketKey)); + } else { + // Multiple txs at same timestamp - treat as batch + const netChange = totalSupplied - totalWithdrawn; + const eventAmount = Number(formatBalance(netChange >= 0n ? netChange : -netChange, decimals)); + dataPoints.push(createDataPoint(ts, 'batch', eventAmount, undefined, txsAtTimestamp.length)); + } + } + } + + // Add final data point with current positions + // Reset to current values + for (const market of markets) { + const key = market.uniqueKey.toLowerCase(); + marketPositions[key] = BigInt(market.currentSupplyAssets); + } + dataPoints.push(createDataPoint(endTimestamp)); + + const debugInfo: PositionHistoryDebugInfo | null = debug + ? { + inputTransactionsCount: transactions.length, + relevantTransactionsCount: relevantTxs.length, + filteredByMarket, + filteredByType, + filteredByTime, + dataPointsCount: dataPoints.length, + startTimestamp, + endTimestamp, + marketsCount: markets.length, + snapshotFound, + batchingApplied: needsBatching, + batchWindowSeconds, + } + : null; + + if (debug) { + console.log('[PositionHistoryChart] Debug Info:', debugInfo); + console.log('[PositionHistoryChart] Data Points:', dataPoints); + console.log('[PositionHistoryChart] Markets:', marketInfoList); + console.log('[PositionHistoryChart] Relevant Transactions:', relevantTxs.length); + } + + return { dataPoints, markets: marketInfoList, debugInfo }; + }, [markets, loanAssetDecimals, startTimestamp, endTimestamp, transactions, snapshots, debug]); +} diff --git a/src/hooks/useUserPositionsSummaryData.ts b/src/hooks/useUserPositionsSummaryData.ts index fb550767..74d31531 100644 --- a/src/hooks/useUserPositionsSummaryData.ts +++ b/src/hooks/useUserPositionsSummaryData.ts @@ -112,6 +112,8 @@ const useUserPositionsSummaryData = (user: string | undefined, period: EarningsP refetch, loadingStates, actualBlockData: actualBlockData ?? {}, + transactions: txData?.items ?? [], + snapshotsByChain: allSnapshots ?? {}, }; }; diff --git a/src/utils/blockEstimation.ts b/src/utils/blockEstimation.ts index 059eba1d..559bbd6d 100644 --- a/src/utils/blockEstimation.ts +++ b/src/utils/blockEstimation.ts @@ -60,9 +60,7 @@ export async function fetchBlocksWithTimestamps( currentTimestamp: number, ): Promise { // First, estimate all block numbers - const estimatedBlocks = targetTimestamps.map((ts) => - estimateBlockAtTimestamp(chainId, ts, currentBlock, currentTimestamp), - ); + const estimatedBlocks = targetTimestamps.map((ts) => estimateBlockAtTimestamp(chainId, ts, currentBlock, currentTimestamp)); // Fetch all block timestamps in parallel const blockPromises = estimatedBlocks.map(async (blockNum, index) => {