"use client"; import { useState, useEffect } from "react"; import Image from "next/image"; import { Button } from "@heroui/react"; import { useApp } from "@/contexts/AppContext"; import { buttonStyles } from "@/lib/buttonStyles"; import { useAccount } from "wagmi"; import { useReadContract } from "wagmi"; import { formatUnits } from "viem"; import { useLendingSupply, useSuppliedBalance } from "@/hooks/useLendingSupply"; import { useLendingWithdraw } from "@/hooks/useLendingWithdraw"; import { useBorrowBalance, useMaxBorrowable } from "@/hooks/useCollateral"; import { notifyLendingBorrow, notifyLendingRepay } from "@/lib/api/lending"; import { useAppKit } from "@reown/appkit/react"; import { toast } from "sonner"; import { getTxUrl, getContractAddress, abis } from "@/lib/contracts"; import { useTokenDecimalsFromAddress } from "@/hooks/useTokenDecimals"; import { useTokenBySymbol } from "@/hooks/useTokenBySymbol"; interface RepayBorrowDebtProps { refreshTrigger?: number; } export default function RepayBorrowDebt({ refreshTrigger }: RepayBorrowDebtProps) { const { t } = useApp(); const { isConnected } = useAccount(); const { open } = useAppKit(); const [mounted, setMounted] = useState(false); const [activeTab, setActiveTab] = useState<'borrow' | 'repay'>('borrow'); const [amount, setAmount] = useState(''); useEffect(() => { setMounted(true); }, []); // On-chain data const { formattedBalance: suppliedBalance, refetch: refetchSupply } = useSuppliedBalance(); const { formattedBalance: borrowedBalance, refetch: refetchBorrow } = useBorrowBalance(); const { formattedAvailable, available, refetch: refetchMaxBorrowable } = useMaxBorrowable(); // Refresh when collateral changes (triggered by RepaySupplyCollateral) useEffect(() => { if (refreshTrigger === undefined || refreshTrigger === 0) return; refetchBorrow(); refetchMaxBorrowable(); }, [refreshTrigger]); // Borrow = withdraw (takes USDC from protocol, increasing debt) const { status: borrowStatus, error: borrowError, isLoading: isBorrowing, withdrawHash: borrowHash, executeWithdraw: executeBorrow, reset: resetBorrow, } = useLendingWithdraw(); // Repay = supply USDC (reduces debt) const { status: repayStatus, error: repayError, isLoading: isRepaying, approveHash: repayApproveHash, supplyHash: repayHash, executeApproveAndSupply: executeRepay, reset: resetRepay, } = useLendingSupply(); const BORROW_TOAST_ID = 'lending-borrow-tx'; const REPAY_TOAST_ID = 'lending-repay-tx'; // Borrow 交易提交 toast useEffect(() => { if (borrowHash && borrowStatus === 'withdrawing') { toast.loading(t("repay.toast.borrowSubmitted"), { id: BORROW_TOAST_ID, description: t("repay.toast.borrowingNow"), action: { label: t("repay.toast.viewTx"), onClick: () => window.open(getTxUrl(borrowHash), '_blank') }, }); } }, [borrowHash, borrowStatus]); // Repay approve 交易提交 toast useEffect(() => { if (repayApproveHash && repayStatus === 'approving') { toast.loading(t("repay.toast.approvalSubmitted"), { id: REPAY_TOAST_ID, description: t("repay.toast.waitingConfirmation"), action: { label: t("repay.toast.viewTx"), onClick: () => window.open(getTxUrl(repayApproveHash), '_blank') }, }); } }, [repayApproveHash, repayStatus]); // Repay 交易提交 toast useEffect(() => { if (repayHash && repayStatus === 'supplying') { toast.loading(t("repay.toast.repaySubmitted"), { id: REPAY_TOAST_ID, description: t("repay.toast.repayingNow"), action: { label: t("repay.toast.viewTx"), onClick: () => window.open(getTxUrl(repayHash), '_blank') }, }); } }, [repayHash, repayStatus]); // Notify backend after borrow useEffect(() => { if (borrowStatus === 'success' && borrowHash && amount) { toast.success(t("repay.toast.borrowSuccess"), { id: BORROW_TOAST_ID, description: t("repay.toast.borrowSuccessDesc"), duration: 5000, }); notifyLendingBorrow(amount, borrowHash); setTimeout(() => refetchBorrow(), 2000); setAmount(''); setTimeout(resetBorrow, 3000); } }, [borrowStatus]); // Notify backend after repay useEffect(() => { if (repayStatus === 'success' && repayHash && amount) { toast.success(t("repay.toast.repaySuccess"), { id: REPAY_TOAST_ID, description: t("repay.toast.repaySuccessDesc"), duration: 5000, }); notifyLendingRepay(amount, repayHash); setTimeout(() => { refetchBorrow(); refetchSupply(); }, 2000); setAmount(''); setTimeout(resetRepay, 3000); } }, [repayStatus]); // 错误提示 useEffect(() => { if (borrowError) { if (borrowError === 'Transaction cancelled') { toast.dismiss(BORROW_TOAST_ID); } else { toast.error(t("repay.toast.borrowFailed"), { id: BORROW_TOAST_ID, duration: 5000 }); } } }, [borrowError]); useEffect(() => { if (repayError) { if (repayError === 'Transaction cancelled') { toast.dismiss(REPAY_TOAST_ID); } else { toast.error(t("repay.toast.repayFailed"), { id: REPAY_TOAST_ID, duration: 5000 }); } } }, [repayError]); // 从产品 API 获取 USDC token 信息,优先从合约地址读取精度 const usdcToken = useTokenBySymbol('USDC'); const { chainId } = useAccount(); const lendingProxyAddress = chainId ? getContractAddress('lendingProxy', chainId) : undefined; const usdcInputDecimals = useTokenDecimalsFromAddress(usdcToken?.contractAddress, usdcToken?.decimals ?? 18); // 最小借贷数量 const { data: baseBorrowMinRaw } = useReadContract({ address: lendingProxyAddress, abi: abis.lendingProxy, functionName: 'baseBorrowMin', query: { enabled: !!lendingProxyAddress }, }); const baseBorrowMin = baseBorrowMinRaw != null ? parseFloat(formatUnits(baseBorrowMinRaw as bigint, usdcInputDecimals)) : 0; const usdcDisplayDecimals = Math.min(usdcInputDecimals, 6); const truncateDecimals = (s: string, d: number) => { const p = s.split('.'); return p.length > 1 ? `${p[0]}.${p[1].slice(0, d)}` : s; }; const isValidAmount = (v: string) => v !== '' && !isNaN(parseFloat(v)) && parseFloat(v) > 0; const isBelowMin = activeTab === 'borrow' && isValidAmount(amount) && baseBorrowMin > 0 && parseFloat(amount) < baseBorrowMin; const handleAmountChange = (value: string) => { if (value === '') { setAmount(value); return; } if (!/^\d*\.?\d*$/.test(value)) return; const parts = value.split('.'); if (parts.length > 1 && parts[1].length > usdcDisplayDecimals) return; const maxBalance = activeTab === 'borrow' ? parseFloat(formattedAvailable) : parseFloat(borrowedBalance); if (parseFloat(value) > maxBalance) { setAmount(truncateDecimals(activeTab === 'borrow' ? formattedAvailable : borrowedBalance, usdcDisplayDecimals)); return; } setAmount(value); }; const handleAction = () => { if (!amount || parseFloat(amount) <= 0) return; if (activeTab === 'borrow') { executeBorrow(amount); } else { executeRepay(amount); } }; const handleMax = () => { if (activeTab === 'repay') { setAmount(truncateDecimals(borrowedBalance, usdcDisplayDecimals)); } else if (activeTab === 'borrow') { setAmount(truncateDecimals(formattedAvailable, usdcDisplayDecimals)); } }; const isLoading = isBorrowing || isRepaying; const currentStatus = activeTab === 'borrow' ? borrowStatus : repayStatus; const getButtonLabel = () => { if (!isConnected) return t("common.connectWallet"); if (currentStatus === 'approving') return t("repay.approvingUsdc"); if (currentStatus === 'approved') return t("repay.confirmedProcessing"); if (currentStatus === 'withdrawing') return t("repay.borrowing"); if (currentStatus === 'supplying') return t("repay.repaying"); if (currentStatus === 'success') return t("common.success"); if (currentStatus === 'error') return t("common.failed"); if (amount && !isValidAmount(amount)) return t("common.invalidAmount"); if (isBelowMin) return t("repay.minBorrow").replace('{{amount}}', baseBorrowMin.toLocaleString('en-US', { maximumFractionDigits: usdcDisplayDecimals })); return activeTab === 'borrow' ? t("repay.borrow") : t("repay.repay"); }; return (