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('idle') const [error, setError] = useState(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('idle') const [error, setError] = useState(null) const [pendingToken, setPendingToken] = useState(null) const [pendingAmount, setPendingAmount] = useState('') 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, } }