Skip to content
Open
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
48 changes: 9 additions & 39 deletions packages/app/src/features/landing/sections/BalanceSheetSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,17 @@ import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';

import { BalanceItem } from '@/components/ui/BalanceItem';
import { OpticianSans } from '@/fonts';

interface BalanceData {
title: string;
subtitle: string;
value: number;
color: string;
[key: string]: string | number;
}

const BALANCE_DATA: BalanceData[] = [
{
title: 'HYPE (SPOT)',
subtitle: 'Hypercore',
value: 49.42,
color: '#18edeb',
},
{
title: 'HYPE (1x SHORT)',
subtitle: 'Hypercore',
value: 49.42,
color: '#e24e76',
},
{
title: 'USDC (EXTRA MARGIN)',
subtitle: 'Hypercore',
value: 1.15,
color: '#0085ff',
},
{
title: 'USDC (TO BE ALLOCATED)',
subtitle: 'HYPEREVM',
value: 1.01,
color: '#7f99ff',
},
];
import { useBalanceSheet } from '@/hooks';

interface BalanceSheetSectionProps {
id?: string;
}

export const BalanceSheetSection: React.FC<BalanceSheetSectionProps> = ({ id }) => {
export const BalanceSheetSection: React.FC<BalanceSheetSectionProps> = ({
id,
}) => {
const { balanceData } = useBalanceSheet();

return (
<Container id={id}>
<Title>balance SHEET</Title>
Expand All @@ -52,15 +22,15 @@ export const BalanceSheetSection: React.FC<BalanceSheetSectionProps> = ({ id })
<ResponsiveContainer width="100%" height={400}>
<PieChart>
<Pie
data={BALANCE_DATA}
data={balanceData}
cx="50%"
cy="50%"
outerRadius={180}
innerRadius={80}
dataKey="value"
stroke="none"
>
{BALANCE_DATA.map((entry, index) => (
{balanceData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
Expand All @@ -73,7 +43,7 @@ export const BalanceSheetSection: React.FC<BalanceSheetSectionProps> = ({ id })
</CenterGraphic>
</ChartContainer>
<BalanceList>
{BALANCE_DATA.map((item, index) => (
{balanceData.map((item, index) => (
<BalanceItem
key={index}
title={item.title}
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export { useDeposit } from './useDeposit';
export { useWithdraw } from './useWithdraw';
export { useExchangeRate } from './useExchangeRate';
export { useFees } from './useFees';
export { useBalanceSheet } from './useBalanceSheet';

export type { DepositStatus } from './useDeposit';
export type { WithdrawStatus } from './useWithdraw';
export type { WithdrawStatus } from './useWithdraw';
export type { BalanceData } from './useBalanceSheet';
129 changes: 129 additions & 0 deletions packages/app/src/hooks/useBalanceSheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { useReadContract } from 'wagmi';
import { formatUnits } from 'viem';

import { Contracts, CrestAccountantAbi, CrestManagerAbi } from '@/utils/contracts';

export interface BalanceData {
title: string;
subtitle: string;
value: number;
color: string;
[key: string]: string | number;
}

export const useBalanceSheet = () => {
// Get total assets from Accountant
const { data: totalAssets } = useReadContract({
address: Contracts.Crest.Accountant,
abi: CrestAccountantAbi,
functionName: 'getTotalAssets',
});

// Get positions from Manager
const { data: positions } = useReadContract({
address: Contracts.Crest.Manager,
abi: CrestManagerAbi,
functionName: 'getPositions',
});

// Get Core position value
const { data: corePositionValue } = useReadContract({
address: Contracts.Crest.Manager,
abi: CrestManagerAbi,
functionName: 'estimatePositionValue',
});

// Calculate balance sheet data
const balanceData: BalanceData[] = [];
const totalAssetsFormatted = totalAssets && typeof totalAssets === 'bigint'
? Number(formatUnits(totalAssets, 6))
: 0;
const coreValueFormatted = corePositionValue && typeof corePositionValue === 'bigint'
? Number(formatUnits(corePositionValue, 6))
: 0;

if (positions && Array.isArray(positions) && totalAssetsFormatted > 0) {
const [spotPosition, perpPosition] = positions;

// Calculate spot position value (approximate)
const spotSizeFormatted = spotPosition.size ? Number(formatUnits(BigInt(spotPosition.size), 8)) : 0;
const spotEntryPrice = spotPosition.entryPrice ? Number(formatUnits(BigInt(spotPosition.entryPrice), 8)) : 0;
const spotValue = spotSizeFormatted * spotEntryPrice;
const spotPercentage = totalAssetsFormatted > 0 ? (spotValue / totalAssetsFormatted) * 100 : 0;

// Calculate perp position value (approximate)
const perpSizeFormatted = perpPosition.size ? Number(formatUnits(BigInt(perpPosition.size), 8)) : 0;
const perpEntryPrice = perpPosition.entryPrice ? Number(formatUnits(BigInt(perpPosition.entryPrice), 8)) : 0;
const perpValue = perpSizeFormatted * perpEntryPrice;
const perpPercentage = totalAssetsFormatted > 0 ? (perpValue / totalAssetsFormatted) * 100 : 0;

// Remaining USDC (vault balance + other assets)
const remainingValue = totalAssetsFormatted - spotValue - Math.abs(perpValue);
const remainingPercentage = totalAssetsFormatted > 0 ? (remainingValue / totalAssetsFormatted) * 100 : 0;

// Add positions to balance data
if (spotPosition.size > 0) {
balanceData.push({
title: `SPOT ${spotPosition.isLong ? 'LONG' : 'SHORT'}`,
subtitle: 'Hyperliquid Core',
value: Math.max(0, spotPercentage),
color: spotPosition.isLong ? '#18edeb' : '#e24e76',
});
}

if (perpPosition.size > 0) {
balanceData.push({
title: `PERP ${perpPosition.isLong ? 'LONG' : 'SHORT'}`,
subtitle: 'Hyperliquid Core',
value: Math.max(0, perpPercentage),
color: perpPosition.isLong ? '#18edeb' : '#e24e76',
});
}

// Add remaining USDC
if (remainingValue > 0) {
balanceData.push({
title: 'USDC (AVAILABLE)',
subtitle: 'Vault Balance',
value: Math.max(0, remainingPercentage),
color: '#0085ff',
});
}

// Add allocation buffer if needed
const totalPercentage = balanceData.reduce((sum, item) => sum + item.value, 0);
if (totalPercentage < 100) {
balanceData.push({
title: 'USDC (TO BE ALLOCATED)',
subtitle: 'Vault Reserve',
value: Math.max(0, 100 - totalPercentage),
color: '#7f99ff',
});
}
}

// Fallback to static data if no positions or data
if (balanceData.length === 0) {
balanceData.push(
{
title: 'USDC (AVAILABLE)',
subtitle: 'Vault Balance',
value: 98.85,
color: '#0085ff',
},
{
title: 'USDC (TO BE ALLOCATED)',
subtitle: 'Vault Reserve',
value: 1.15,
color: '#7f99ff',
}
);
}

return {
balanceData,
totalAssets: totalAssetsFormatted,
corePositionValue: coreValueFormatted,
positions,
};
};