import { useAccount, useReadContract, useReadContracts } from 'wagmi' import { formatUnits } from 'viem' import { abis, getContractAddress } from '@/lib/contracts' import { useEffect, useState } from 'react' import { Token } from '@/lib/api/tokens' import { useTokenList } from './useTokenList' /** * 健康因子状态 */ export type HealthFactorStatus = 'safe' | 'warning' | 'danger' | 'critical' /** * 健康因子结果 */ export interface HealthFactorResult { healthFactor: bigint formattedHealthFactor: string status: HealthFactorStatus utilization: number // 利用率百分比 borrowValue: bigint // 借款价值(USD,1e18精度) collateralValue: bigint // 抵押品价值(USD,1e18精度) isLoading: boolean refetch: () => void } /** * 获取用户健康因子 * * 健康因子定义: * - > 1.5: 安全(绿色) * - 1.2 ~ 1.5: 警告(黄色) * - 1.0 ~ 1.2: 危险(橙色) * - < 1.0: 临界/可被清算(红色) * - MaxUint256: 无债务 */ export function useHealthFactor(): HealthFactorResult { const { address, chainId } = useAccount() const { bySymbol } = useTokenList() const usdcToken = bySymbol['USDC'] const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined // 1. 获取用户基本信息(principal) const { data: userBasic, refetch: refetchUserBasic } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'userBasic', args: address ? [address] : undefined, query: { enabled: !!address && !!lendingProxyAddress, }, }) const principal = userBasic !== undefined ? (userBasic as bigint) : 0n // 如果没有借款(principal >= 0),提前返回 const hasDebt = principal < 0n const shouldCalculate = hasDebt && !!address && !!lendingProxyAddress // 2. 获取借款索引 const { data: borrowIndex, refetch: refetchBorrowIndex } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'borrowIndex', query: { enabled: shouldCalculate, }, }) // 3. 获取价格预言机地址 const { data: priceFeedAddress } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'lendingPriceSource', query: { enabled: shouldCalculate, }, }) // 4. 获取 baseToken 地址 const { data: baseToken } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'baseToken', query: { enabled: shouldCalculate, }, }) // 5. 直接获取资产列表(YT-A, YT-B, YT-C) const assetIndices = [0, 1, 2] as const const assetListContracts = assetIndices.map((i) => ({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'assetList' as const, args: [BigInt(i)], })) const { data: assetListData } = useReadContracts({ contracts: assetListContracts as any, query: { enabled: shouldCalculate && assetListContracts.length > 0, }, }) const assetList = assetListData?.map((item) => item.result as `0x${string}`).filter(Boolean) || [] // 7. 使用 useState 来管理计算结果 const [result, setResult] = useState({ healthFactor: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), // MaxUint256 formattedHealthFactor: '∞', status: 'safe', utilization: 0, borrowValue: 0n, collateralValue: 0n, isLoading: true, refetch: () => { refetchUserBasic() refetchBorrowIndex() refetchCollateral() refetchPrices() }, }) // 8. 获取所有抵押品余额和配置 const collateralContracts = assetList.flatMap((asset) => [ // 获取用户抵押品余额 { address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'userCollateral' as const, args: [address, asset], }, // 获取资产配置 { address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'assetConfigs' as const, args: [asset], }, ]) const { data: collateralData, refetch: refetchCollateral } = useReadContracts({ contracts: collateralContracts as any, query: { enabled: shouldCalculate && assetList.length > 0, }, }) // 9. 获取所有资产价格(包括 baseToken) const priceContracts = [baseToken, ...assetList].filter(Boolean).map((token) => ({ address: priceFeedAddress, abi: [ { type: 'function', name: 'getPrice', inputs: [{ name: 'asset', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', }, ], functionName: 'getPrice' as const, args: [token], })) const { data: priceData, refetch: refetchPrices } = useReadContracts({ contracts: priceContracts as any, query: { enabled: shouldCalculate && !!priceFeedAddress && priceContracts.length > 0, }, }) // 10. 计算健康因子 useEffect(() => { if (!shouldCalculate) { // 没有债务,返回最大健康因子 setResult({ healthFactor: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), formattedHealthFactor: '∞', status: 'safe', utilization: 0, borrowValue: 0n, collateralValue: 0n, isLoading: false, refetch: () => { refetchUserBasic() }, }) return } if (!borrowIndex || !priceData || !collateralData || !baseToken || !chainId) { return } try { // 计算实际债务(principal * borrowIndex / 1e18) const balance = (principal * (borrowIndex as bigint)) / BigInt(1e18) const debt = -balance // 转为正数 // 获取 baseToken 价格(第一个价格) const basePrice = priceData[0]?.result as bigint if (!basePrice) return // 计算债务价值(USD) const baseDecimals = usdcToken?.onChainDecimals ?? usdcToken?.decimals ?? 18 const debtValue = (debt * basePrice) / BigInt(10 ** baseDecimals) if (debtValue === 0n) { setResult({ healthFactor: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), formattedHealthFactor: '∞', status: 'safe', utilization: 0, borrowValue: 0n, collateralValue: 0n, isLoading: false, refetch: () => { refetchUserBasic() }, }) return } // 计算抵押品总价值(使用 liquidateCollateralFactor) let totalCollateralValue = 0n // assetConfigs 返回元组: [asset, decimals, borrowCollateralFactor, liquidateCollateralFactor, liquidationFactor, supplyCap] type AssetConfigTuple = readonly [string, number, bigint, bigint, bigint, bigint] for (let i = 0; i < assetList.length; i++) { const collateralAmount = collateralData[i * 2]?.result as bigint const assetConfig = collateralData[i * 2 + 1]?.result as AssetConfigTuple | undefined const assetPrice = priceData[i + 1]?.result as bigint if (collateralAmount > 0n && assetConfig && assetPrice) { // assetConfig[1] = decimals, assetConfig[3] = liquidateCollateralFactor const assetScale = 10n ** BigInt(Number(assetConfig[1])) const collateralValue = (collateralAmount * assetPrice) / assetScale // 应用清算折扣系数(liquidateCollateralFactor) const discountedValue = (collateralValue * assetConfig[3]) / BigInt(1e18) totalCollateralValue += discountedValue } } // 计算健康因子 = 抵押品价值 / 债务价值(1e18 精度) const healthFactor = (totalCollateralValue * BigInt(1e18)) / debtValue // 计算利用率(债务/抵押品) const utilization = totalCollateralValue > 0n ? Number((debtValue * BigInt(10000)) / totalCollateralValue) / 100 : 0 // 确定健康因子状态 let status: HealthFactorStatus const hfNumber = Number(formatUnits(healthFactor, 18)) if (hfNumber >= 1.5) { status = 'safe' } else if (hfNumber >= 1.2) { status = 'warning' } else if (hfNumber >= 1.0) { status = 'danger' } else { status = 'critical' } // 格式化健康因子显示 const formattedHealthFactor = hfNumber >= 10 ? hfNumber.toFixed(1) : hfNumber >= 1 ? hfNumber.toFixed(2) : hfNumber.toFixed(3) setResult({ healthFactor, formattedHealthFactor, status, utilization, borrowValue: debtValue, collateralValue: totalCollateralValue, isLoading: false, refetch: () => { refetchUserBasic() refetchBorrowIndex() refetchCollateral() refetchPrices() }, }) } catch (error) { console.error('Health factor calculation error:', error) setResult((prev) => ({ ...prev, isLoading: false })) } }, [ shouldCalculate, principal, borrowIndex, priceData, collateralData, baseToken, chainId, assetList.length, refetchUserBasic, ]) return result } /** * 获取用户借款余额 */ export function useBorrowBalance() { const { address, chainId } = useAccount() const { bySymbol } = useTokenList() const usdcToken = bySymbol['USDC'] const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: borrowBalance, refetch } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'borrowBalanceOf', args: address ? [address] : undefined, query: { enabled: !!address && !!lendingProxyAddress, }, }) const usdcDecimals = usdcToken?.onChainDecimals ?? usdcToken?.decimals ?? 18 const formattedBalance = borrowBalance ? formatUnits(borrowBalance as bigint, usdcDecimals) : '0.00' return { balance: borrowBalance as bigint | undefined, formattedBalance, refetch, } } /** * 获取用户在特定资产的抵押品余额 */ export function useCollateralBalance(token: Token | undefined) { const { address, chainId } = useAccount() const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: collateralBalance, refetch } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'userCollateral', args: address && token?.contractAddress ? [address, token.contractAddress as `0x${string}`] : undefined, query: { enabled: !!address && !!lendingProxyAddress && !!token?.contractAddress, }, }) const decimals = token?.onChainDecimals ?? token?.decimals ?? 18 const formattedBalance = collateralBalance ? formatUnits(collateralBalance as bigint, decimals) : '0.00' return { balance: collateralBalance as bigint | undefined, formattedBalance, refetch, } } /** * 获取协议利用率 */ export function useProtocolUtilization() { const { chainId } = useAccount() const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: utilization } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'getUtilization', query: { enabled: !!lendingProxyAddress, }, }) // utilization 是 1e18 精度的百分比 const utilizationPercent = utilization ? Number(formatUnits(utilization as bigint, 18)) * 100 : 0 return { utilization: utilization as bigint | undefined, utilizationPercent, } } const SECONDS_PER_YEAR = 365 * 24 * 60 * 60 function calcAPY(annualAprBigint: bigint | undefined): { apr: number; apy: number } { if (!annualAprBigint) return { apr: 0, apy: 0 } // getSupplyRate / getBorrowRate return annual APR with 1e18 precision const annualApr = Number(formatUnits(annualAprBigint, 18)) // e.g. 0.05 = 5% const perSecondRate = annualApr / SECONDS_PER_YEAR const apy = perSecondRate > 0 ? (Math.pow(1 + perSecondRate, SECONDS_PER_YEAR) - 1) * 100 : 0 return { apr: annualApr * 100, apy } } /** * 获取当前供应 APY * getSupplyRate() 返回年化 APR(1e18 精度,uint64) */ export function useSupplyAPY() { const { chainId } = useAccount() const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: supplyRate } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'getSupplyRate', query: { enabled: !!lendingProxyAddress }, }) const { apr, apy } = calcAPY(supplyRate as bigint | undefined) return { supplyRate: supplyRate as bigint | undefined, apr, apy, } } /** * 获取当前借款 APR/APY * getBorrowRate() 返回年化 APR(1e18 精度,uint64) */ export function useBorrowAPR() { const { chainId } = useAccount() const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: borrowRate } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'getBorrowRate', query: { enabled: !!lendingProxyAddress }, }) const { apr, apy } = calcAPY(borrowRate as bigint | undefined) return { borrowRate: borrowRate as bigint | undefined, apr, apy, } }