init: 初始化 AssetX 项目仓库

包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、
antdesign(管理后台)、landingpage(营销落地页)、
数据库 SQL 和配置文件。
This commit is contained in:
2026-03-27 11:26:43 +00:00
commit 2ee4553b71
634 changed files with 988255 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
import { useState, useCallback, useEffect } from 'react'
import { useAccount, useWriteContract, useWaitForTransactionReceipt, useReadContract } from 'wagmi'
import { parseUnits, formatUnits } from 'viem'
import { abis, getContractAddress } from '@/lib/contracts'
import { Token } from '@/lib/api/tokens'
import { handleContractCatchError, isValidAmount, parseContractError } from '@/lib/errors'
type WithdrawCollateralStatus = 'idle' | 'withdrawing' | 'success' | 'error'
/**
* Hook for withdrawing collateral (YT tokens) from lending protocol
*/
export function useWithdrawCollateral() {
const { address, chainId } = useAccount()
const [status, setStatus] = useState<WithdrawCollateralStatus>('idle')
const [error, setError] = useState<string | null>(null)
const {
writeContractAsync: withdrawWrite,
data: withdrawHash,
isPending: isWithdrawPending,
reset: resetWithdraw,
} = useWriteContract()
const {
isLoading: isWithdrawConfirming,
isSuccess: isWithdrawSuccess,
isError: isWithdrawError,
error: withdrawReceiptError,
} = useWaitForTransactionReceipt({ hash: withdrawHash })
const executeWithdrawCollateral = useCallback(async (token: Token, amount: string) => {
if (status !== 'idle') return false
if (!address || !chainId || !token?.contractAddress || !amount) {
setError('Missing required parameters')
return false
}
if (!isValidAmount(amount)) {
setError('Invalid amount')
return false
}
try {
setError(null)
setStatus('withdrawing')
const lendingProxyAddress = getContractAddress('lendingProxy', chainId)
if (!lendingProxyAddress) {
throw new Error('Contract not deployed on this chain')
}
const decimals = token.onChainDecimals ?? token.decimals
const amountInWei = parseUnits(amount, decimals)
await withdrawWrite({
address: lendingProxyAddress,
abi: abis.lendingProxy,
functionName: 'withdrawCollateral',
args: [token.contractAddress as `0x${string}`, amountInWei],
})
return true
} catch (err: unknown) {
handleContractCatchError(err, 'Withdraw collateral failed', setError, setStatus as (s: string) => void)
return false
}
}, [address, chainId, withdrawWrite, status])
useEffect(() => {
if (isWithdrawSuccess && status === 'withdrawing') {
setStatus('success')
}
}, [isWithdrawSuccess, status])
useEffect(() => {
if (isWithdrawError && status === 'withdrawing') {
setError(parseContractError(withdrawReceiptError))
setStatus('error')
}
}, [isWithdrawError, status, withdrawReceiptError])
const reset = useCallback(() => {
setStatus('idle')
setError(null)
resetWithdraw()
}, [resetWithdraw])
return {
status,
error,
isLoading: isWithdrawPending || isWithdrawConfirming,
withdrawHash,
executeWithdrawCollateral,
reset,
}
}
type CollateralStatus = 'idle' | 'approving' | 'approved' | 'supplying' | 'success' | 'error'
/**
* Hook for supplying collateral (YT tokens) to lending protocol
*/
export function useLendingCollateral() {
const { address, chainId } = useAccount()
const [status, setStatus] = useState<CollateralStatus>('idle')
const [error, setError] = useState<string | null>(null)
const [pendingToken, setPendingToken] = useState<Token | null>(null)
const [pendingAmount, setPendingAmount] = useState<string>('')
const {
writeContractAsync: approveWrite,
data: approveHash,
isPending: isApprovePending,
reset: resetApprove,
} = useWriteContract()
const {
isLoading: isApproveConfirming,
isSuccess: isApproveSuccess,
isError: isApproveError,
error: approveReceiptError,
} = useWaitForTransactionReceipt({ hash: approveHash })
const {
writeContractAsync: supplyWrite,
data: supplyHash,
isPending: isSupplyPending,
reset: resetSupply,
} = useWriteContract()
const {
isLoading: isSupplyConfirming,
isSuccess: isSupplySuccess,
isError: isSupplyError,
error: supplyReceiptError,
} = useWaitForTransactionReceipt({ hash: supplyHash })
const executeApprove = useCallback(async (token: Token, amount: string) => {
if (status !== 'idle') return false
if (!address || !chainId || !token?.contractAddress || !amount) {
setError('Missing required parameters')
return false
}
if (!isValidAmount(amount)) {
setError('Invalid amount')
return false
}
try {
setError(null)
setStatus('approving')
const lendingProxyAddress = getContractAddress('lendingProxy', chainId)
if (!lendingProxyAddress) {
throw new Error('Contract not deployed on this chain')
}
const decimals = token.onChainDecimals ?? token.decimals
const amountInWei = parseUnits(amount, decimals)
await approveWrite({
address: token.contractAddress as `0x${string}`,
abi: abis.YTToken,
functionName: 'approve',
args: [lendingProxyAddress, amountInWei],
})
return true
} catch (err: unknown) {
handleContractCatchError(err, 'Approve failed', setError, setStatus as (s: string) => void)
return false
}
}, [address, chainId, approveWrite, status])
const executeSupplyCollateral = useCallback(async (token: Token, amount: string) => {
if (status === 'supplying' || status === 'success' || status === 'error') return false
if (!address || !chainId || !token?.contractAddress || !amount) {
setError('Missing required parameters')
return false
}
if (!isValidAmount(amount)) {
setError('Invalid amount')
return false
}
try {
setError(null)
setStatus('supplying')
const lendingProxyAddress = getContractAddress('lendingProxy', chainId)
if (!lendingProxyAddress) {
throw new Error('Contract not deployed on this chain')
}
const decimals = token.onChainDecimals ?? token.decimals
const amountInWei = parseUnits(amount, decimals)
await supplyWrite({
address: lendingProxyAddress,
abi: abis.lendingProxy,
functionName: 'supplyCollateral',
args: [token.contractAddress as `0x${string}`, amountInWei],
})
return true
} catch (err: unknown) {
handleContractCatchError(err, 'Supply collateral failed', setError, setStatus as (s: string) => void)
return false
}
}, [address, chainId, supplyWrite, status])
const executeApproveAndSupply = useCallback(async (token: Token, amount: string) => {
if (status !== 'idle') return
setPendingToken(token)
setPendingAmount(amount)
const approveSuccess = await executeApprove(token, amount)
if (!approveSuccess) return
}, [executeApprove, status])
// Auto-execute supply when approve is successful
useEffect(() => {
if (isApproveSuccess && status === 'approving' && pendingToken && pendingAmount) {
setStatus('approved')
executeSupplyCollateral(pendingToken, pendingAmount)
}
}, [isApproveSuccess, status, pendingToken, pendingAmount, executeSupplyCollateral])
useEffect(() => {
if (isApproveError && status === 'approving') {
setError(parseContractError(approveReceiptError))
setStatus('error')
}
}, [isApproveError, status, approveReceiptError])
useEffect(() => {
if (isSupplySuccess && status === 'supplying') {
setStatus('success')
}
}, [isSupplySuccess, status])
useEffect(() => {
if (isSupplyError && status === 'supplying') {
setError(parseContractError(supplyReceiptError))
setStatus('error')
}
}, [isSupplyError, status, supplyReceiptError])
const reset = useCallback(() => {
setStatus('idle')
setError(null)
setPendingToken(null)
setPendingAmount('')
resetApprove()
resetSupply()
}, [resetApprove, resetSupply])
return {
status,
error,
isLoading: isApprovePending || isApproveConfirming || isSupplyPending || isSupplyConfirming,
approveHash,
supplyHash,
executeApprove,
executeSupplyCollateral,
executeApproveAndSupply,
reset,
}
}
/**
* Query user's wallet balance of a specific YT token (ERC20 balanceOf)
*/
export function useYTWalletBalance(token: Token | undefined) {
const { address } = useAccount()
const { data: balance, isLoading, refetch } = useReadContract({
address: token?.contractAddress as `0x${string}` | undefined,
abi: abis.YTToken,
functionName: 'balanceOf',
args: address ? [address] : undefined,
query: {
enabled: !!address && !!token?.contractAddress,
retry: false,
},
})
const decimals = token?.onChainDecimals ?? token?.decimals ?? 18
const formattedBalance = balance
? formatUnits(balance as bigint, decimals)
: '0.00'
return {
balance: balance as bigint || 0n,
formattedBalance,
isLoading,
refetch,
}
}
/**
* Query 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: balance,
isLoading,
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,
retry: false,
},
})
const decimals = token?.onChainDecimals ?? token?.decimals ?? 18
const formattedBalance = balance
? formatUnits(balance as bigint, decimals)
: '0.00'
return {
balance: balance as bigint || 0n,
formattedBalance,
isLoading,
refetch,
}
}