Files
assetx/webapp/hooks/useCollateral.ts
default 2ee4553b71 init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、
antdesign(管理后台)、landingpage(营销落地页)、
数据库 SQL 和配置文件。
2026-03-27 11:26:43 +00:00

268 lines
8.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
}