import { useAccount, useReadContract, useReadContracts } from 'wagmi' import { formatUnits } from 'viem' import { abis, getContractAddress } from '@/lib/contracts' import { Token } from '@/lib/api/tokens' import { useTokenList } from './useTokenList' /** * Hook to query user's collateral balance for a specific YT token */ export function useCollateralBalance(token: Token | undefined) { const { address, chainId } = useAccount() const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: collateralBalance, refetch, isLoading } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'getCollateral', args: address && token?.contractAddress ? [address, token.contractAddress as `0x${string}`] : undefined, query: { enabled: !!address && !!token?.contractAddress && !!lendingProxyAddress, }, }) const decimals = token?.onChainDecimals ?? token?.decimals ?? 18 const formattedBalance = collateralBalance ? formatUnits(collateralBalance as bigint, decimals) : '0' return { balance: collateralBalance as bigint | undefined, formattedBalance, isLoading, refetch, } } /** * Hook to query user's borrow balance (in USDC) */ 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, isLoading } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'borrowBalanceOf', args: address ? [address] : undefined, query: { enabled: !!address && !!chainId && !!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, isLoading, refetch, } } /** * Hook to query user's balance (positive = supply, negative = borrow) */ export function useAccountBalance() { const { address, chainId } = useAccount() const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined const { data: supplyBalance, refetch: refetchSupply, isLoading: isLoadingSupply } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'supplyBalanceOf', args: address ? [address] : undefined, query: { enabled: !!address && !!chainId && !!lendingProxyAddress, }, }) const { data: borrowBalance, refetch: refetchBorrow, isLoading: isLoadingBorrow } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'borrowBalanceOf', args: address ? [address] : undefined, query: { enabled: !!address && !!chainId && !!lendingProxyAddress, }, }) const supplyValue = (supplyBalance as bigint) || 0n const borrowValue = (borrowBalance as bigint) || 0n const netBalance = supplyValue - borrowValue return { balance: netBalance, isSupply: supplyValue > 0n, isBorrow: borrowValue > 0n, isLoading: isLoadingSupply || isLoadingBorrow, refetch: () => { refetchSupply() refetchBorrow() }, } } /** * Hook to calculate LTV (Loan-to-Value) ratio * LTV = (Borrow Balance / Collateral Value) * 100 */ export function useLTV() { const { bySymbol } = useTokenList() const ytA = bySymbol['YT-A'] const ytB = bySymbol['YT-B'] const ytC = bySymbol['YT-C'] const { formattedBalance: borrowBalance } = useBorrowBalance() const { value: valueA } = useCollateralValue(ytA) const { value: valueB } = useCollateralValue(ytB) const { value: valueC } = useCollateralValue(ytC) const totalCollateralValue = parseFloat(valueA) + parseFloat(valueB) + parseFloat(valueC) const borrowValue = parseFloat(borrowBalance) const ltv = totalCollateralValue > 0 ? (borrowValue / totalCollateralValue) * 100 : 0 return { ltv: ltv.toFixed(2), ltvRaw: ltv, isLoading: false, } } /** * Hook to get YT token price from its contract */ export function useYTPrice(token: Token | undefined) { const { data: ytPrice, isLoading } = useReadContract({ address: token?.contractAddress as `0x${string}` | undefined, abi: abis.YTToken, functionName: 'ytPrice', query: { enabled: !!token?.contractAddress, }, }) // ytPrice 返回 30 位精度的价格(PRICE_PRECISION = 1e30) const formattedPrice = ytPrice ? formatUnits(ytPrice as bigint, 30) : '0' return { price: ytPrice as bigint | undefined, formattedPrice, isLoading, } } /** * Hook to calculate collateral value in USD */ export function useCollateralValue(token: Token | undefined) { const { formattedBalance } = useCollateralBalance(token) const { formattedPrice } = useYTPrice(token) const value = parseFloat(formattedBalance) * parseFloat(formattedPrice) return { value: value.toFixed(2), valueRaw: value, } } type AssetConfig = { asset: `0x${string}` decimals: number borrowCollateralFactor: bigint liquidateCollateralFactor: bigint liquidationFactor: bigint supplyCap: bigint } /** * Hook to calculate total borrow capacity and available borrowable amount */ export function useMaxBorrowable() { const { chainId } = useAccount() const { bySymbol } = useTokenList() const ytA = bySymbol['YT-A'] const ytB = bySymbol['YT-B'] const ytC = bySymbol['YT-C'] const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined // Collateral balances const { formattedBalance: balA, refetch: refetchA } = useCollateralBalance(ytA) const { formattedBalance: balB, refetch: refetchB } = useCollateralBalance(ytB) const { formattedBalance: balC, refetch: refetchC } = useCollateralBalance(ytC) // YT token prices (ytPrice(), 30-decimal precision) const { formattedPrice: priceA } = useYTPrice(ytA) const { formattedPrice: priceB } = useYTPrice(ytB) const { formattedPrice: priceC } = useYTPrice(ytC) // Borrow collateral factors from lendingProxy.assetConfigs const ytAddresses = [ytA, ytB, ytC] .filter(t => !!t?.contractAddress) .map(t => t!.contractAddress as `0x${string}`) const configContracts = ytAddresses.map((addr) => ({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'assetConfigs' as const, args: [addr], })) const { data: configData } = useReadContracts({ contracts: configContracts as any, query: { enabled: !!lendingProxyAddress && configContracts.length > 0 }, }) type ConfigTuple = readonly [string, number, bigint, bigint, bigint, bigint] const getConfig = (i: number): ConfigTuple | undefined => configData?.[i]?.result as ConfigTuple | undefined const calcCapacity = (bal: string, price: string, config: ConfigTuple | undefined) => { if (!config || !config[2]) return 0 const factor = Number(config[2]) / 1e18 return parseFloat(bal) * parseFloat(price) * factor } const capacityA = calcCapacity(balA, priceA, getConfig(0)) const capacityB = calcCapacity(balB, priceB, getConfig(1)) const capacityC = calcCapacity(balC, priceC, getConfig(2)) const totalCapacity = capacityA + capacityB + capacityC const { formattedBalance: borrowedBalance, refetch: refetchBorrow } = useBorrowBalance() const currentBorrow = parseFloat(borrowedBalance) || 0 const available = Math.max(0, totalCapacity - currentBorrow) const refetch = () => { refetchA() refetchB() refetchC() refetchBorrow() } return { totalCapacity, available, formattedCapacity: totalCapacity.toFixed(2), formattedAvailable: available.toFixed(2), refetch, } }