diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6c2cbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Markdown files +*.md + +# Node modules +node_modules/ + +# Build output +dist/ +dist-ssr/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor +.vscode/ +.idea/ +*.sw? +.DS_Store + +# Local env files +*.local + +# Claude plans +.claude/ diff --git a/frontend/src/App.css b/frontend/src/App.css index 0cb1691..7635f28 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,3 +1,33 @@ +/* 统一颜色变量 */ +:root { + /* 主色 */ + --color-primary: #1976d2; + --color-primary-dark: #1565c0; + --color-primary-light: #e3f2fd; + + /* 文字 */ + --color-text: #333; + --color-text-secondary: #666; + --color-text-muted: #999; + + /* 背景 */ + --color-bg: #f5f5f5; + --color-bg-card: #fff; + --color-bg-section: #f8f9fa; + + /* 边框 */ + --color-border: #e0e0e0; + --color-border-light: #eee; + + /* 状态色 - 仅用于状态指示 */ + --color-success: #4caf50; + --color-success-bg: #e8f5e9; + --color-warning: #ff9800; + --color-warning-bg: #fff3e0; + --color-error: #f44336; + --color-error-bg: #ffebee; +} + * { box-sizing: border-box; margin: 0; @@ -6,8 +36,8 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; - background-color: #f5f5f5; - color: #333; + background-color: var(--color-bg); + color: var(--color-text); line-height: 1.6; } @@ -453,42 +483,62 @@ body { .test-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 12px; + grid-template-columns: 1fr 1fr; + gap: 8px; } .test-card { + display: flex; + align-items: stretch; background: #fff; border: 1px solid #e0e0e0; - border-radius: 8px; - padding: 14px; + border-radius: 6px; + overflow: hidden; } -.test-header { +.test-card-left { + flex: 1; + padding: 8px 10px; display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; + flex-direction: column; + gap: 2px; } -.test-name { +.test-card-left .test-name { + font-size: 12px; font-weight: 600; - font-size: 14px; - color: #1a1a1a; + color: #333; } -.test-error { +.test-card-left .test-error { + font-size: 10px; + color: #999; +} + +.test-card-left .test-desc { + font-size: 10px; + color: #666; + margin: 0; +} + +.test-card-right { + display: flex; + align-items: center; + padding: 0 10px; + background: #f8f9fa; + border-left: 1px solid #e0e0e0; +} + +.test-card-right .btn { font-size: 11px; - color: #f57c00; - background: #fff3e0; - padding: 2px 8px; - border-radius: 4px; + padding: 4px 12px; } .test-desc { - font-size: 12px; - color: #666; - margin-bottom: 12px; + font-size: 11px; + color: #999; + margin: 0; + line-height: 1.3; } .test-actions { diff --git a/frontend/src/components/FactoryPanel.tsx b/frontend/src/components/FactoryPanel.tsx index 427ff14..dc4392b 100644 --- a/frontend/src/components/FactoryPanel.tsx +++ b/frontend/src/components/FactoryPanel.tsx @@ -22,6 +22,17 @@ export function FactoryPanel() { ytPrice: '', }) const [showPermissionTest, setShowPermissionTest] = useState(false) + const [showOwnerConfig, setShowOwnerConfig] = useState(false) + const [newDefaultHardCap, setNewDefaultHardCap] = useState('') + const [showBatchOps, setShowBatchOps] = useState(false) + const [batchPriceForm, setBatchPriceForm] = useState({ wusdPrice: '1', ytPrice: '1' }) + const [batchHardCapForm, setBatchHardCapForm] = useState('') + const [selectedVaultsForBatch, setSelectedVaultsForBatch] = useState([]) + const [showAdvanced, setShowAdvanced] = useState(false) + const [newImplementation, setNewImplementation] = useState('') + const [upgradeVaultAddr, setUpgradeVaultAddr] = useState('') + const [upgradeImplAddr, setUpgradeImplAddr] = useState('') + const [batchRedemptionTime, setBatchRedemptionTime] = useState('') const { data: allVaults, refetch: refetchVaults } = useReadContract({ address: CONTRACTS.FACTORY, @@ -41,12 +52,18 @@ export function FactoryPanel() { functionName: 'owner', }) - const { data: defaultHardCap } = useReadContract({ + const { data: defaultHardCap, refetch: refetchDefaultHardCap } = useReadContract({ address: CONTRACTS.FACTORY, abi: FACTORY_ABI, functionName: 'defaultHardCap', }) + const { data: vaultImplementation } = useReadContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'vaultImplementation', + }) + const { writeContract, data: hash, isPending, reset, error: writeError, status: writeStatus } = useWriteContract() const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ @@ -56,6 +73,7 @@ export function FactoryPanel() { useEffect(() => { if (isSuccess) { refetchVaults() + refetchDefaultHardCap() reset() } }, [isSuccess]) @@ -108,6 +126,125 @@ export function FactoryPanel() { }) } + const handleSetDefaultHardCap = () => { + if (!newDefaultHardCap) return + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setDefaultHardCap', + args: [parseUnits(newDefaultHardCap, 18)], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 批量更新价格 + const handleBatchUpdatePrices = () => { + if (selectedVaultsForBatch.length === 0) return + const wusdPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.wusdPrice, 18)) + const ytPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.ytPrice, 18)) + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'updateVaultPricesBatch', + args: [selectedVaultsForBatch as `0x${string}`[], wusdPrices, ytPrices], + gas: GAS_CONFIG.VERY_COMPLEX, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 批量设置硬顶 + const handleBatchSetHardCap = () => { + if (selectedVaultsForBatch.length === 0 || !batchHardCapForm) return + const hardCaps = selectedVaultsForBatch.map(() => parseUnits(batchHardCapForm, 18)) + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setHardCapBatch', + args: [selectedVaultsForBatch as `0x${string}`[], hardCaps], + gas: GAS_CONFIG.VERY_COMPLEX, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 切换选中金库 + const toggleVaultSelection = (vaultAddr: string) => { + setSelectedVaultsForBatch(prev => + prev.includes(vaultAddr) + ? prev.filter(v => v !== vaultAddr) + : [...prev, vaultAddr] + ) + } + + // 全选/取消全选 + const toggleSelectAll = () => { + if (allVaults && selectedVaultsForBatch.length === allVaults.length) { + setSelectedVaultsForBatch([]) + } else if (allVaults) { + setSelectedVaultsForBatch([...allVaults]) + } + } + + // 批量设置赎回时间 + const handleBatchSetRedemptionTime = () => { + if (selectedVaultsForBatch.length === 0 || !batchRedemptionTime) return + const timestamp = BigInt(Math.floor(new Date(batchRedemptionTime).getTime() / 1000)) + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setVaultNextRedemptionTimeBatch', + args: [selectedVaultsForBatch as `0x${string}`[], timestamp], + gas: GAS_CONFIG.VERY_COMPLEX, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置金库实现地址 + const handleSetVaultImplementation = () => { + if (!newImplementation) return + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setVaultImplementation', + args: [newImplementation as `0x${string}`], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 升级单个金库 + const handleUpgradeVault = () => { + if (!upgradeVaultAddr || !upgradeImplAddr) return + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'upgradeVault', + args: [upgradeVaultAddr as `0x${string}`, upgradeImplAddr as `0x${string}`], + gas: GAS_CONFIG.COMPLEX, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 批量升级金库 + const handleBatchUpgradeVault = () => { + if (selectedVaultsForBatch.length === 0 || !upgradeImplAddr) return + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'upgradeVaultBatch', + args: [selectedVaultsForBatch as `0x${string}`[], upgradeImplAddr as `0x${string}`], + gas: GAS_CONFIG.VERY_COMPLEX, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + // 权限测试函数 const runPermissionTest = (testType: string) => { const testVault = allVaults && allVaults.length > 0 ? allVaults[0] : CONTRACTS.VAULTS.YT_A @@ -168,6 +305,10 @@ export function FactoryPanel() { {t('factory.owner')}: {owner || '-'} +
+ {t('factory.vaultImpl')}: + {vaultImplementation || '-'} +
{t('factory.defaultHardCap')}: {defaultHardCap ? formatUnits(defaultHardCap, 18) : '0'} @@ -332,14 +473,290 @@ export function FactoryPanel() { )} + {/* Owner 配置区域 */} +
+
setShowOwnerConfig(!showOwnerConfig)} + > +

{t('factory.ownerConfig')}

+ {showOwnerConfig ? '▼' : '▶'} +
+ + {showOwnerConfig && ( +
+ {/* 设置默认硬顶 */} +
+ +
+ setNewDefaultHardCap(e.target.value)} + placeholder={defaultHardCap ? formatUnits(defaultHardCap, 18) : '1000000'} + className="input" + style={{ flex: 1 }} + /> + +
+
+
+ )} +
+ + {/* 批量操作区域 */} +
+
setShowBatchOps(!showBatchOps)} + > +

{t('factory.batchOperations')}

+ {showBatchOps ? '▼' : '▶'} +
+ + {showBatchOps && ( +
+ {/* 金库选择 */} +
+
+ + +
+
+ {allVaults?.map((vault, index) => ( + + ))} +
+
+ {t('factory.selectedCount')}: {selectedVaultsForBatch.length} +
+
+ + {/* 批量更新价格 */} +
+
{t('factory.batchUpdatePrices')}
+
+
+ + setBatchPriceForm({ ...batchPriceForm, wusdPrice: e.target.value })} + className="input" + style={{ fontSize: '13px' }} + /> +
+
+ + setBatchPriceForm({ ...batchPriceForm, ytPrice: e.target.value })} + className="input" + style={{ fontSize: '13px' }} + /> +
+ +
+
+ + {/* 批量设置硬顶 */} +
+
{t('factory.batchSetHardCap')}
+
+ setBatchHardCapForm(e.target.value)} + placeholder="100000" + className="input" + style={{ flex: 1, fontSize: '13px' }} + /> + +
+
+ + {/* 批量设置赎回时间 */} +
+
{t('factory.batchSetRedemptionTime')}
+
+ setBatchRedemptionTime(e.target.value)} + className="input" + style={{ flex: 1, fontSize: '13px' }} + /> + +
+
+
+ )} +
+ + {/* 高级功能区域 */} +
+
setShowAdvanced(!showAdvanced)} + > +

{t('factory.advancedFunctions')}

+ {showAdvanced ? '▼' : '▶'} +
+ + {showAdvanced && ( +
+ {/* 警告提示 */} +
+ {t('factory.advancedWarning')} +
+ + {/* 设置金库实现 */} +
+
{t('factory.setVaultImplementation')}
+
+ setNewImplementation(e.target.value)} + placeholder="0x..." + className="input" + style={{ flex: 1, fontSize: '12px' }} + /> + +
+
+ {t('factory.currentImpl')}: {vaultImplementation || '-'} +
+
+ + {/* 升级单个金库 */} +
+
{t('factory.upgradeVault')}
+
+ + setUpgradeImplAddr(e.target.value)} + placeholder={t('factory.newImplAddress')} + className="input" + style={{ fontSize: '12px' }} + /> +
+ +
+ + {/* 批量升级金库 */} +
+
{t('factory.batchUpgradeVault')}
+
+ {t('factory.selectedCount')}: {selectedVaultsForBatch.length} ({t('factory.selectInBatchSection')}) +
+
+ setUpgradeImplAddr(e.target.value)} + placeholder={t('factory.newImplAddress')} + className="input" + style={{ flex: 1, fontSize: '12px' }} + /> + +
+
+
+ )} +
+ {/* 权限测试区域 */} -
-
setShowPermissionTest(!showPermissionTest)} style={{ cursor: 'pointer' }}> -

{t('test.permissionTests')} {showPermissionTest ? '[-]' : '[+]'}

+
+
setShowPermissionTest(!showPermissionTest)} + > +

{t('test.permissionTests')}

+ {showPermissionTest ? '▼' : '▶'}
{showPermissionTest && ( - <> +
{t('test.permissionHint')} {isOwner && (You are Owner)} @@ -347,28 +764,27 @@ export function FactoryPanel() {
-
+
{t('test.updatePriceNotOwner')} - OwnableUnauthorizedAccount + Unauthorized +

{t('test.updatePriceNotOwnerDesc')}

+
+
+
-

{t('test.updatePriceNotOwnerDesc')}

-
-
-
+
{t('test.setManagerNotOwner')} - OwnableUnauthorizedAccount + Unauthorized +

{t('test.setManagerNotOwnerDesc')}

+
+
+
-

{t('test.setManagerNotOwnerDesc')}

-
- +
)}
diff --git a/frontend/src/components/LPPanel.tsx b/frontend/src/components/LPPanel.tsx index ccb3f73..396536a 100644 --- a/frontend/src/components/LPPanel.tsx +++ b/frontend/src/components/LPPanel.tsx @@ -1,6 +1,6 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi' +import { useAccount, useReadContract, useReadContracts, useWriteContract, useWaitForTransactionReceipt } from 'wagmi' import { parseUnits, formatUnits } from 'viem' import { CONTRACTS, @@ -11,20 +11,50 @@ import { YT_VAULT_ABI, YT_PRICE_FEED_ABI, WUSD_ABI, - YT_ASSET_VAULT_ABI + YT_ASSET_VAULT_ABI, + FACTORY_ABI } from '../config/contracts' import { useTransactions } from '../context/TransactionContext' import type { TransactionType } from '../context/TransactionContext' import { useToast } from './Toast' import { TransactionHistory } from './TransactionHistory' -// Token list for the LP pool -const POOL_TOKENS = [ - { address: CONTRACTS.VAULTS.YT_A, symbol: 'YT-A', name: 'YT Token A' }, - { address: CONTRACTS.VAULTS.YT_B, symbol: 'YT-B', name: 'YT Token B' }, - { address: CONTRACTS.VAULTS.YT_C, symbol: 'YT-C', name: 'YT Token C' }, - { address: CONTRACTS.WUSD, symbol: 'WUSD', name: 'Wrapped USD' }, -] +/** + * ============================================================================ + * 已知问题 (Known Issue) - 2024-12-17 + * ============================================================================ + * + * 【问题描述】 + * LP 流动性池操作 (addLiquidity, removeLiquidity, swapYT) 当前会失败 + * 错误: PriceChangeTooLarge (0xa8eb64ed) + * + * 【根本原因】 + * YTPriceFeed 合约需要从 YT 代币读取 assetPrice 变量,但 YTAssetVault 合约 + * 只有 ytPrice,没有 assetPrice。 + * + * 调用链: + * addLiquidity() → YTVault.buyUSDY() → YTPriceFeed.getMinPrice() + * → 读取 YT-A.assetPrice() → 不存在,返回异常值 + * → currentPrice vs cachedPrice 差异 > 5% → PriceChangeTooLarge + * + * 【解决方案】(需要合约端修复) + * YTAssetVault 合约需要添加: + * function assetPrice() external view returns (uint256) { + * return ytPrice; + * } + * + * 【文档参考】 + * - ytLp池子合约流程文档.md 第 320-322 行 (价格获取流程) + * - 合约文档.md 第 4093-4118 行 (YTPriceFeed.getPriceInfo ABI) + * ============================================================================ + */ + +// ERC20 基础 ABI - 用于读取代币 symbol 和 name +const ERC20_BASE_ABI = [ + { inputs: [], name: 'symbol', outputs: [{ type: 'string' }], stateMutability: 'view', type: 'function' }, + { inputs: [], name: 'name', outputs: [{ type: 'string' }], stateMutability: 'view', type: 'function' }, + { inputs: [{ type: 'address' }], name: 'balanceOf', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' }, +] as const export function LPPanel() { const { t } = useTranslation() @@ -66,6 +96,7 @@ export function LPPanel() { const [activeTab, setActiveTab] = useState<'add' | 'remove' | 'swap'>('add') const [showBoundaryTest, setShowBoundaryTest] = useState(false) const [showAdminConfig, setShowAdminConfig] = useState(false) + const [showDebugInfo, setShowDebugInfo] = useState(false) const [priceConfigForm, setPriceConfigForm] = useState<{ token: string price: string @@ -87,6 +118,131 @@ export function LPPanel() { maxUsdyAmount: '1000000', // 最大限额 USDY }) const [wusdPriceSourceInput, setWusdPriceSourceInput] = useState(CONTRACTS.WUSD) + const [diagToken, setDiagToken] = useState(CONTRACTS.VAULTS.YT_A) // 诊断代币选择 + const [ytPriceForm, setYtPriceForm] = useState<{ + ytPrice: string + }>({ + ytPrice: '1', // 新的 ytPrice,单位是 USD + }) + const [clearWhitelistToken, setClearWhitelistToken] = useState('') // 移除白名单代币选择 + const [stableTokenConfig, setStableTokenConfig] = useState<{ token: string; isStable: boolean }>({ + token: CONTRACTS.VAULTS.YT_A, + isStable: false, + }) + + // ===== 动态获取 LP 池代币列表 ===== + const { data: rawPoolTokenAddresses } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'getAllPoolTokens', + }) + + // 去重处理(合约可能返回重复地址) + const poolTokenAddresses = useMemo(() => { + if (!rawPoolTokenAddresses || rawPoolTokenAddresses.length === 0) return [] + const seen = new Set() + return (rawPoolTokenAddresses as string[]).filter((addr: string) => { + const lower = addr.toLowerCase() + if (seen.has(lower)) return false + seen.add(lower) + return true + }) + }, [rawPoolTokenAddresses]) + + // 批量读取代币 symbol 和 name + const tokenInfoContracts = useMemo(() => { + if (!poolTokenAddresses || poolTokenAddresses.length === 0) return [] + return poolTokenAddresses.flatMap((addr: string) => [ + { address: addr as `0x${string}`, abi: ERC20_BASE_ABI, functionName: 'symbol' as const }, + { address: addr as `0x${string}`, abi: ERC20_BASE_ABI, functionName: 'name' as const }, + ]) + }, [poolTokenAddresses]) + + const { data: tokenInfoResults } = useReadContracts({ + contracts: tokenInfoContracts, + }) + + // 批量读取用户各代币余额 + const balanceContracts = useMemo(() => { + if (!poolTokenAddresses || poolTokenAddresses.length === 0 || !address) return [] + return poolTokenAddresses.map((addr: string) => ({ + address: addr as `0x${string}`, + abi: ERC20_BASE_ABI, + functionName: 'balanceOf' as const, + args: [address] as const, + })) + }, [poolTokenAddresses, address]) + + const { data: balanceResults, refetch: refetchTokenBalances } = useReadContracts({ + contracts: balanceContracts, + }) + + // 批量读取白名单状态 + const whitelistContracts = useMemo(() => { + if (!poolTokenAddresses || poolTokenAddresses.length === 0) return [] + return poolTokenAddresses.map((addr: string) => ({ + address: CONTRACTS.YT_VAULT as `0x${string}`, + abi: YT_VAULT_ABI, + functionName: 'whitelistedTokens' as const, + args: [addr] as const, + })) + }, [poolTokenAddresses]) + + const { data: whitelistResults } = useReadContracts({ + contracts: whitelistContracts, + }) + + // 批量读取 usdyAmounts + const usdyAmountsContracts = useMemo(() => { + if (!poolTokenAddresses || poolTokenAddresses.length === 0) return [] + return poolTokenAddresses.map((addr: string) => ({ + address: CONTRACTS.YT_VAULT as `0x${string}`, + abi: YT_VAULT_ABI, + functionName: 'usdyAmounts' as const, + args: [addr] as const, + })) + }, [poolTokenAddresses]) + + const { data: usdyAmountsResults } = useReadContracts({ + contracts: usdyAmountsContracts, + }) + + // 批量读取稳定币状态 + const stableTokensContracts = useMemo(() => { + if (!poolTokenAddresses || poolTokenAddresses.length === 0) return [] + return poolTokenAddresses.map((addr: string) => ({ + address: CONTRACTS.YT_PRICE_FEED as `0x${string}`, + abi: YT_PRICE_FEED_ABI, + functionName: 'stableTokens' as const, + args: [addr] as const, + })) + }, [poolTokenAddresses]) + + const { data: stableTokensResults } = useReadContracts({ + contracts: stableTokensContracts, + }) + + // 构建代币列表(动态) + const POOL_TOKENS = useMemo(() => { + if (!poolTokenAddresses || poolTokenAddresses.length === 0) return [] + return poolTokenAddresses.map((addr: string, index: number) => { + const symbol = tokenInfoResults?.[index * 2]?.result as string | undefined + const name = tokenInfoResults?.[index * 2 + 1]?.result as string | undefined + const balance = balanceResults?.[index]?.result as bigint | undefined + const isWhitelisted = whitelistResults?.[index]?.result as boolean | undefined + const usdyAmount = usdyAmountsResults?.[index]?.result as bigint | undefined + const isStable = stableTokensResults?.[index]?.result as boolean | undefined + return { + address: addr, + symbol: symbol || `Token ${index}`, + name: name || `Unknown Token ${index}`, + balance, + isWhitelisted, + usdyAmount, + isStable, + } + }) + }, [poolTokenAddresses, tokenInfoResults, balanceResults, whitelistResults, usdyAmountsResults, stableTokensResults]) // Read pool data const { data: ytLPBalance, refetch: refetchBalance } = useReadContract({ @@ -102,35 +258,6 @@ export function LPPanel() { functionName: 'totalSupply', }) - // 读取用户各代币余额 - const { data: ytABalance } = useReadContract({ - address: CONTRACTS.VAULTS.YT_A, - abi: WUSD_ABI, - functionName: 'balanceOf', - args: address ? [address] : undefined, - }) - - const { data: ytBBalance } = useReadContract({ - address: CONTRACTS.VAULTS.YT_B, - abi: WUSD_ABI, - functionName: 'balanceOf', - args: address ? [address] : undefined, - }) - - const { data: ytCBalance } = useReadContract({ - address: CONTRACTS.VAULTS.YT_C, - abi: WUSD_ABI, - functionName: 'balanceOf', - args: address ? [address] : undefined, - }) - - const { data: wusdBalanceLP } = useReadContract({ - address: CONTRACTS.WUSD, - abi: WUSD_ABI, - functionName: 'balanceOf', - args: address ? [address] : undefined, - }) - const { data: ytLPPrice } = useReadContract({ address: CONTRACTS.YT_REWARD_ROUTER, abi: YT_REWARD_ROUTER_ABI, @@ -144,75 +271,6 @@ export function LPPanel() { args: [true], }) - // 检查各代币是否被 LP 池白名单 - const { data: isYtAWhitelisted } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'whitelistedTokens', - args: [CONTRACTS.VAULTS.YT_A], - }) - - const { data: isYtBWhitelisted } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'whitelistedTokens', - args: [CONTRACTS.VAULTS.YT_B], - }) - - const { data: isYtCWhitelisted } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'whitelistedTokens', - args: [CONTRACTS.VAULTS.YT_C], - }) - - const { data: isWusdWhitelisted } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'whitelistedTokens', - args: [CONTRACTS.WUSD], - }) - - // 读取当前选中代币在池子中的数量 - const { data: poolAmountYtA } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'poolAmounts', - args: [CONTRACTS.VAULTS.YT_A], - }) - - // 读取当前选中代币的价格 - const { data: tokenPriceYtA } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'getMinPrice', - args: [CONTRACTS.VAULTS.YT_A], - }) - - // 读取代币权重 - const { data: tokenWeightYtA } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'tokenWeights', - args: [CONTRACTS.VAULTS.YT_A], - }) - - // 读取代币精度配置 - const { data: tokenDecimalsYtA } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'tokenDecimals', - args: [CONTRACTS.VAULTS.YT_A], - }) - - // 读取最大 USDY 限额 - const { data: maxUsdyAmountYtA } = useReadContract({ - address: CONTRACTS.YT_VAULT, - abi: YT_VAULT_ABI, - functionName: 'maxUsdyAmounts', - args: [CONTRACTS.VAULTS.YT_A], - }) - // 读取 YTVault 管理员地址 const { data: vaultGov } = useReadContract({ address: CONTRACTS.YT_VAULT, @@ -227,30 +285,6 @@ export function LPPanel() { functionName: 'priceFeed', }) - // 直接从 YTPriceFeed 读取 YT-A 价格 (getMinPrice) - const { data: priceFeedYtAPrice } = useReadContract({ - address: CONTRACTS.YT_PRICE_FEED, - abi: YT_PRICE_FEED_ABI, - functionName: 'getMinPrice', - args: [CONTRACTS.VAULTS.YT_A], - }) - - // 直接从 YTPriceFeed 读取 YT-A lastPrice (原始设置的价格) - const { data: priceFeedYtALastPrice } = useReadContract({ - address: CONTRACTS.YT_PRICE_FEED, - abi: YT_PRICE_FEED_ABI, - functionName: 'lastPrice', - args: [CONTRACTS.VAULTS.YT_A], - }) - - // 直接从 YTPriceFeed 读取 YT-A spreadBasisPoints - const { data: priceFeedYtASpread } = useReadContract({ - address: CONTRACTS.YT_PRICE_FEED, - abi: YT_PRICE_FEED_ABI, - functionName: 'spreadBasisPoints', - args: [CONTRACTS.VAULTS.YT_A], - }) - // 读取 YTPriceFeed 管理员地址 const { data: priceFeedGov } = useReadContract({ address: CONTRACTS.YT_PRICE_FEED, @@ -272,34 +306,97 @@ export function LPPanel() { functionName: 'maxPriceChangeBps', }) - // 直接读取 YT-A 合约的 ytPrice (旧的18位精度) - const { data: ytAContractYtPrice } = useReadContract({ - address: CONTRACTS.VAULTS.YT_A, + // 使用 getPriceInfo 获取完整价格信息 (支持选择代币) + const { data: diagPriceInfo } = useReadContract({ + address: CONTRACTS.YT_PRICE_FEED, + abi: YT_PRICE_FEED_ABI, + functionName: 'getPriceInfo', + args: [diagToken as `0x${string}`], + }) + + // 读取诊断代币的 ytPrice + const { data: diagYtPrice } = useReadContract({ + address: diagToken as `0x${string}`, abi: YT_ASSET_VAULT_ABI, functionName: 'ytPrice', }) - // 直接读取 YT-A 合约的 assetPrice (YTPriceFeed 期望的30位精度接口) - const { data: ytAContractAssetPrice } = useReadContract({ - address: CONTRACTS.VAULTS.YT_A, + // 读取诊断代币的 assetPrice + const { data: diagAssetPrice } = useReadContract({ + address: diagToken as `0x${string}`, abi: YT_ASSET_VAULT_ABI, functionName: 'assetPrice', }) - // 读取 WUSD 的 getMinPrice - const { data: wusdGetMinPrice } = useReadContract({ + // 诊断代币的动态读取 + const { data: diagTokenDecimals } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'tokenDecimals', + args: [diagToken as `0x${string}`], + }) + + const { data: diagTokenWeight } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'tokenWeights', + args: [diagToken as `0x${string}`], + }) + + const { data: diagWhitelisted } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'whitelistedTokens', + args: [diagToken as `0x${string}`], + }) + + const { data: diagMaxUsdyAmount } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'maxUsdyAmounts', + args: [diagToken as `0x${string}`], + }) + + const { data: diagPoolAmount } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'poolAmounts', + args: [diagToken as `0x${string}`], + }) + + const { data: diagUsdyAmount } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'usdyAmounts', + args: [diagToken as `0x${string}`], + }) + + const { data: diagLastPrice } = useReadContract({ + address: CONTRACTS.YT_PRICE_FEED, + abi: YT_PRICE_FEED_ABI, + functionName: 'lastPrice', + args: [diagToken as `0x${string}`], + }) + + const { data: diagSpread } = useReadContract({ + address: CONTRACTS.YT_PRICE_FEED, + abi: YT_PRICE_FEED_ABI, + functionName: 'spreadBasisPoints', + args: [diagToken as `0x${string}`], + }) + + const { data: diagGetMinPrice } = useReadContract({ address: CONTRACTS.YT_PRICE_FEED, abi: YT_PRICE_FEED_ABI, functionName: 'getMinPrice', - args: [CONTRACTS.WUSD], + args: [diagToken as `0x${string}`], }) - // 使用 getPriceInfo 获取完整价格信息 - const { data: ytAPriceInfo } = useReadContract({ + const { data: diagStableToken } = useReadContract({ address: CONTRACTS.YT_PRICE_FEED, abi: YT_PRICE_FEED_ABI, - functionName: 'getPriceInfo', - args: [CONTRACTS.VAULTS.YT_A], + functionName: 'stableTokens', + args: [diagToken as `0x${string}`], }) // 读取 YTPriceFeed 配置的 WUSD 地址 @@ -325,6 +422,28 @@ export function LPPanel() { args: [priceConfigForm.token as `0x${string}`], }) + // 读取选中代币的 ytPrice (从代币合约) + const { data: selectedTokenYtPrice } = useReadContract({ + address: priceConfigForm.token as `0x${string}`, + abi: YT_ASSET_VAULT_ABI, + functionName: 'ytPrice', + }) + + // 读取选中代币的 wusdPrice (从代币合约) + const { data: selectedTokenWusdPrice } = useReadContract({ + address: priceConfigForm.token as `0x${string}`, + abi: YT_ASSET_VAULT_ABI, + functionName: 'wusdPrice', + }) + + // 读取选中代币的 lastPrice (从 YTPriceFeed) + const { data: selectedTokenLastPrice } = useReadContract({ + address: CONTRACTS.YT_PRICE_FEED, + abi: YT_PRICE_FEED_ABI, + functionName: 'lastPrice', + args: [priceConfigForm.token as `0x${string}`], + }) + const { data: cooldownDuration } = useReadContract({ address: CONTRACTS.YT_POOL_MANAGER, abi: YT_POOL_MANAGER_ABI, @@ -338,6 +457,35 @@ export function LPPanel() { args: address ? [address] : undefined, }) + // 读取紧急模式状态 + const { data: emergencyMode } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'emergencyMode', + }) + + // 读取 swap 开关状态 + const { data: swapEnabled } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'swapEnabled', + }) + + // 读取总权重 + const { data: totalTokenWeights } = useReadContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'totalTokenWeights', + }) + + // 读取用户账户价值 + const { data: accountValue } = useReadContract({ + address: CONTRACTS.YT_REWARD_ROUTER, + abi: YT_REWARD_ROUTER_ABI, + functionName: 'getAccountValue', + args: address ? [address] : undefined, + }) + // Check token allowance for the selected token const { data: tokenAllowance, refetch: refetchAllowance } = useReadContract({ address: addLiquidityForm.token as `0x${string}`, @@ -398,6 +546,7 @@ export function LPPanel() { pendingTxRef.current = null } refetchBalance() + refetchTokenBalances() refetchAllowance() refetchYtLPAllowance() reset() @@ -595,6 +744,38 @@ export function LPPanel() { }) } + // 同步价格: 从代币合约读取 ytPrice() 并更新到 YTPriceFeed.lastPrice + const handleSyncPrice = () => { + if (!address || !selectedTokenYtPrice) return + recordTx('test', formatUnits(selectedTokenYtPrice as bigint, 30), 'SyncPrice') + writeContract({ + address: CONTRACTS.YT_PRICE_FEED, + abi: YT_PRICE_FEED_ABI, + functionName: 'forceUpdatePrice', + args: [priceConfigForm.token as `0x${string}`, selectedTokenYtPrice as bigint], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 更新代币的 ytPrice (通过 Factory 合约) + const handleUpdateYtPrice = () => { + if (!address || !ytPriceForm.ytPrice || !selectedTokenWusdPrice) return + // ytPrice 精度是 30 位小数 (与 lastPrice 一致) + const newYtPrice = parseUnits(ytPriceForm.ytPrice, 30) + recordTx('test', ytPriceForm.ytPrice, 'UpdateYtPrice') + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'updateVaultPrices', + args: [priceConfigForm.token as `0x${string}`, selectedTokenWusdPrice as bigint, newYtPrice], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + // 设置代币价差 (管理员功能) const handleSetSpread = () => { if (!address || !priceConfigForm.spreadBps) return @@ -650,6 +831,68 @@ export function LPPanel() { }) } + // 移除白名单代币 (管理员功能) + const handleClearWhitelistedToken = () => { + if (!address || !clearWhitelistToken) return + const tokenSymbol = POOL_TOKENS.find(t => t.address.toLowerCase() === clearWhitelistToken.toLowerCase())?.symbol || 'Unknown' + recordTx('test', tokenSymbol, 'ClearWhitelistedToken') + writeContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'clearWhitelistedToken', + args: [clearWhitelistToken as `0x${string}`], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置稳定币标记 (管理员功能) + const handleSetStableToken = () => { + if (!address) return + const tokenSymbol = POOL_TOKENS.find(t => t.address.toLowerCase() === stableTokenConfig.token.toLowerCase())?.symbol || 'Unknown' + recordTx('test', `${tokenSymbol} -> ${stableTokenConfig.isStable ? 'Stable' : 'Non-Stable'}`, 'SetStableToken') + writeContract({ + address: CONTRACTS.YT_PRICE_FEED, + abi: YT_PRICE_FEED_ABI, + functionName: 'setStableToken', + args: [stableTokenConfig.token as `0x${string}`, stableTokenConfig.isStable], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置紧急模式 (管理员功能) + const handleSetEmergencyMode = (enabled: boolean) => { + if (!address) return + recordTx('test', enabled ? 'ON' : 'OFF', 'SetEmergencyMode') + writeContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'setEmergencyMode', + args: [enabled], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置 Swap 开关 (管理员功能) + const handleSetSwapEnabled = (enabled: boolean) => { + if (!address) return + recordTx('test', enabled ? 'ON' : 'OFF', 'SetSwapEnabled') + writeContract({ + address: CONTRACTS.YT_VAULT, + abi: YT_VAULT_ABI, + functionName: 'setSwapEnabled', + args: [enabled], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + // Boundary test function const runBoundaryTest = (testType: string) => { if (!address) return @@ -816,172 +1059,68 @@ export function LPPanel() {

{t('lp.title')}

- {/* Pool Info */} -
-
- {t('lp.rewardRouter')}: - {CONTRACTS.YT_REWARD_ROUTER} + {/* Pool Info - Compact Layout */} +
+ {/* 合约地址行 */} +
+ Router: {CONTRACTS.YT_REWARD_ROUTER.slice(0, 8)}...{CONTRACTS.YT_REWARD_ROUTER.slice(-6)} + ytLP: {CONTRACTS.YT_LP_TOKEN.slice(0, 8)}...{CONTRACTS.YT_LP_TOKEN.slice(-6)}
-
- {t('lp.ytLPToken')}: - {CONTRACTS.YT_LP_TOKEN} -
-
- {t('lp.poolAUM')}: - {aumInUsdy ? formatUnits(aumInUsdy, 18) : '0'} USDY -
-
- {t('lp.ytLPPrice')}: - {ytLPPrice ? formatUnits(ytLPPrice, 30) : '1'} USDY -
-
- {t('lp.totalSupply')}: - {ytLPTotalSupply ? formatUnits(ytLPTotalSupply, 18) : '0'} ytLP -
-
- {t('lp.yourBalance')}: - {ytLPBalance ? formatUnits(ytLPBalance, 18) : '0'} ytLP -
-
- {t('lp.cooldownRemaining')}: - {formatCooldown(getCooldownRemaining())} + {/* 池子数据 + 用户数据 - 网格布局 */} +
+
AUM: {aumInUsdy ? Number(formatUnits(aumInUsdy, 18)).toFixed(2) : '0'} USDY
+
ytLP价格: {ytLPPrice ? Number(formatUnits(ytLPPrice, 30)).toFixed(6) : '1'}
+
总供应: {ytLPTotalSupply ? Number(formatUnits(ytLPTotalSupply, 18)).toFixed(2) : '0'}
+
你的ytLP: {ytLPBalance ? Number(formatUnits(ytLPBalance, 18)).toFixed(2) : '0'}
+
冷却: {formatCooldown(getCooldownRemaining())}
+
账户价值: ${accountValue ? Number(formatUnits(accountValue, 30)).toFixed(6) : '0'}
+
+ 紧急模式:{' '} + {emergencyMode ? 'ON' : '正常'} +
+
+ Swap:{' '} + {swapEnabled ? '开启' : '关闭'} +
+
总权重: {totalTokenWeights ? totalTokenWeights.toString() : '0'}
- {/* 用户代币余额及白名单状态 */} + {/* 用户代币余额及白名单状态 - 动态渲染 */}

{t('lp.yourTokenBalances')}

-
-
- YT-A {isYtAWhitelisted ? '✓' : } - {ytABalance ? formatUnits(ytABalance, 18) : '0'} -
-
- YT-B {isYtBWhitelisted ? '✓' : } - {ytBBalance ? formatUnits(ytBBalance, 18) : '0'} -
-
- YT-C {isYtCWhitelisted ? '✓' : } - {ytCBalance ? formatUnits(ytCBalance, 18) : '0'} -
-
- WUSD {isWusdWhitelisted ? '✓' : } - {wusdBalanceLP ? formatUnits(wusdBalanceLP, 18) : '0'} -
-
-

- ✓ = {t('lp.whitelisted')} | = {t('lp.notWhitelisted')} -

-
- - {/* 池子状态调试信息 */} -
-

池子状态 (调试)

-
-
- YT-A 池中数量: - {poolAmountYtA !== undefined ? formatUnits(poolAmountYtA, 18) : '加载中...'} -
-
- YT-A 代币价格: - {tokenPriceYtA !== undefined ? formatUnits(tokenPriceYtA, 30) : '加载中...'} -
-
- YT-A 权重: - {tokenWeightYtA !== undefined ? tokenWeightYtA.toString() : '加载中...'} -
-
- YT-A 白名单: - - {isYtAWhitelisted === undefined ? '加载中...' : (isYtAWhitelisted ? '是 ✓' : '否 ✗')} - -
-
- YT-A tokenDecimals: - 0n ? '#4caf50' : '#f44336' }}> - {tokenDecimalsYtA !== undefined ? tokenDecimalsYtA.toString() : '加载中...'} - {tokenDecimalsYtA !== undefined && tokenDecimalsYtA === 0n && ' (未配置!)'} - -
-
- YT-A maxUsdyAmounts: - 0n ? '#4caf50' : '#f44336' }}> - {maxUsdyAmountYtA !== undefined ? formatUnits(maxUsdyAmountYtA, 18) : '加载中...'} - {maxUsdyAmountYtA !== undefined && maxUsdyAmountYtA === 0n && ' (未配置!)'} - -
-
-
-

- YTVault Gov: {vaultGov || '加载中...'} -

-

- YTVault priceFeed: {vaultPriceFeed || '加载中...'} - {vaultPriceFeed && CONTRACTS.YT_PRICE_FEED.toLowerCase() !== (vaultPriceFeed as string).toLowerCase() && ( - - [不匹配! 应为: {CONTRACTS.YT_PRICE_FEED}] - + + + + + + + + + + + + {POOL_TOKENS.length === 0 ? ( + + + + ) : ( + POOL_TOKENS.map((token) => ( + + + + + + + + )) )} -

-

- YTPriceFeed.lastPrice(YT-A): {priceFeedYtALastPrice !== undefined ? `$${formatUnits(priceFeedYtALastPrice, 30)}` : '加载中...'} - {priceFeedYtALastPrice !== undefined && priceFeedYtALastPrice === 0n && ( - [价格为0! 需要 forceUpdatePrice] - )} -

-

- YT-A合约.ytPrice: {ytAContractYtPrice !== undefined ? `$${formatUnits(ytAContractYtPrice, 18)}` : '加载中...'} - {ytAContractYtPrice !== undefined && ytAContractYtPrice === 0n && ( - [价格为0! 这是根本原因!] - )} -

-

- YTPriceFeed.spreadBasisPoints(YT-A): {priceFeedYtASpread !== undefined ? `${priceFeedYtASpread.toString()} bps (${Number(priceFeedYtASpread) / 100}%)` : '加载中...'} -

-

- YTPriceFeed.wusdPriceSource: {wusdPriceSource || '加载中...'} -

-

- YTPriceFeed.maxPriceChangeBps: {maxPriceChangeBps !== undefined ? `${maxPriceChangeBps.toString()} bps (${Number(maxPriceChangeBps) / 100}%)` : '加载中...'} -

-

- YTPriceFeed.getMinPrice(YT-A): {priceFeedYtAPrice !== undefined ? `$${formatUnits(priceFeedYtAPrice, 30)}` : '加载失败'} -

- -
-

- addLiquidity 失败诊断: -

-
    -
  • YT-A合约.ytPrice (18位): {ytAContractYtPrice !== undefined ? `$${formatUnits(ytAContractYtPrice, 18)}` : '加载失败'}
  • -
  • - YT-A合约.assetPrice (30位): {ytAContractAssetPrice !== undefined ? `$${formatUnits(ytAContractAssetPrice, 30)} OK` : '不存在! YT合约需要实现assetPrice()接口'} -
  • -
  • YTPriceFeed.lastPrice(YT-A): {priceFeedYtALastPrice !== undefined ? (priceFeedYtALastPrice === 0n ? '0 [需要设置!]' : `$${formatUnits(priceFeedYtALastPrice, 30)} OK`) : '加载失败'}
  • -
  • YTPriceFeed.getMinPrice(YT-A): {priceFeedYtAPrice !== undefined ? `$${formatUnits(priceFeedYtAPrice, 30)} OK` : '加载失败'}
  • -
  • YTPriceFeed.getMinPrice(WUSD): {wusdGetMinPrice !== undefined ? `$${formatUnits(wusdGetMinPrice, 30)} OK` : '加载失败'}
  • -
  • YTVault.getMinPrice(YT-A): {tokenPriceYtA !== undefined ? (tokenPriceYtA === 0n ? '0' : `$${formatUnits(tokenPriceYtA, 30)} OK`) : '加载失败'}
  • -
  • YTPriceFeed.WUSD: {priceFeedWusdAddress ? {priceFeedWusdAddress as string} : '加载中...'}
  • -
  • WUSD地址匹配: {priceFeedWusdAddress && CONTRACTS.WUSD.toLowerCase() === (priceFeedWusdAddress as string).toLowerCase() ? 'OK' : `不匹配! 应为: ${CONTRACTS.WUSD}`}
  • -
  • priceFeed 地址匹配: {vaultPriceFeed && CONTRACTS.YT_PRICE_FEED.toLowerCase() === (vaultPriceFeed as string).toLowerCase() ? 'OK' : '不匹配'}
  • -
-
- - {/* getPriceInfo 完整诊断 */} - {ytAPriceInfo && ( -
-

- YTPriceFeed.getPriceInfo(YT-A): -

-
    -
  • currentPrice: ${formatUnits((ytAPriceInfo as any)[0], 30)}
  • -
  • cachedPrice: ${formatUnits((ytAPriceInfo as any)[1], 30)}
  • -
  • maxPrice: ${formatUnits((ytAPriceInfo as any)[2], 30)}
  • -
  • minPrice: ${formatUnits((ytAPriceInfo as any)[3], 30)}
  • -
  • spread: {(ytAPriceInfo as any)[4]?.toString()} bps
  • -
-
- )} + +
{t('lp.selectToken')}{t('common.balance')}{t('lp.usdyAmount')}{t('lp.isStableToken')}{t('lp.whitelisted')}
Loading...
{token.symbol}{token.balance ? formatUnits(token.balance, 18) : '0'}{token.usdyAmount ? formatUnits(token.usdyAmount, 18) : '0'} + {token.isStable ? Yes : No} + + {token.isWhitelisted ? Yes : No} +

{/* Tab Navigation */} @@ -1008,82 +1147,204 @@ export function LPPanel() { {/* Add Liquidity Form */} {activeTab === 'add' && ( -
-

{t('lp.addLiquidity')}

-

{t('lp.addLiquidityDesc')}

+
+

{t('lp.addLiquidityDesc')}

-
-
- + {/* 紧凑表单布局 */} +
+
+
- -
- +
+ setAddLiquidityForm({ ...addLiquidityForm, amount: e.target.value })} placeholder="0.0" className="input" + style={{ fontSize: '13px' }} />
- -
- +
+ setAddLiquidityForm({ ...addLiquidityForm, slippage: e.target.value })} placeholder="0.5" className="input" + style={{ fontSize: '13px' }} step="0.1" />
- {/* 显示当前授权额度 */} -
- 当前授权额度 ({getTokenSymbol(addLiquidityForm.token)}): - BigInt(0) ? '#4caf50' : '#f44336' }}> - {tokenAllowance !== undefined ? formatUnits(tokenAllowance, 18) : '加载中...'} - + {/* 授权额度 + 按钮 - 紧凑行 */} +
+ + 授权: BigInt(0) ? '#4caf50' : '#f44336' }}> + {tokenAllowance !== undefined ? formatUnits(tokenAllowance, 18) : '...'} + + + {needsApproval() ? ( + + ) : ( + + )}
- {needsApproval() ? ( - - ) : ( - - )} +

池子状态 (调试)

+ {showDebugInfo ? '▼' : '▶'} +
+ + {showDebugInfo && (<> + {/* 全局配置 */} +
+

全局配置

+
+
YTVault.gov: {vaultGov ? `${(vaultGov as string).slice(0, 6)}...${(vaultGov as string).slice(-4)}` : '...'}
+
YTPriceFeed.gov: {priceFeedGov ? `${(priceFeedGov as string).slice(0, 6)}...${(priceFeedGov as string).slice(-4)}` : '...'}
+
wusdPriceSource: {wusdPriceSource ? `${(wusdPriceSource as string).slice(0, 6)}...${(wusdPriceSource as string).slice(-4)}` : '...'}
+
maxPriceChangeBps: {maxPriceChangeBps !== undefined ? `${maxPriceChangeBps.toString()} (${Number(maxPriceChangeBps) / 100}%)` : '...'}
+
priceFeed匹配: {vaultPriceFeed && CONTRACTS.YT_PRICE_FEED.toLowerCase() === (vaultPriceFeed as string).toLowerCase() ? 'OK' : 'X'}
+
WUSD匹配: {priceFeedWusdAddress && CONTRACTS.WUSD.toLowerCase() === (priceFeedWusdAddress as string).toLowerCase() ? 'OK' : 'X'}
+
+
+ + {/* 代币诊断选择器 */} +
+
+ 代币诊断: + +
+ + {/* 代币基本信息 */} +
+
地址: {diagToken.slice(0, 8)}...{diagToken.slice(-6)}
+
白名单: {diagWhitelisted ? 'Yes' : 'No'}
+
decimals: 0n ? '#4caf50' : '#f44336' }}>{diagTokenDecimals?.toString() || '0'}
+
权重: {diagTokenWeight?.toString() || '0'}
+
池中数量: {diagPoolAmount !== undefined ? formatUnits(diagPoolAmount, 18) : '...'}
+
USDY数量: {diagUsdyAmount !== undefined ? formatUnits(diagUsdyAmount, 18) : '...'}
+
maxUsdyAmount: 0n ? '#4caf50' : '#f44336' }}>{diagMaxUsdyAmount !== undefined ? (diagMaxUsdyAmount > 0n ? formatUnits(diagMaxUsdyAmount, 18) : '0') : '...'}
+
稳定币: {diagStableToken ? 'Yes' : 'No'}
+
+ + {/* 价格信息 */} +
+

价格信息

+
+
合约.ytPrice: {diagYtPrice !== undefined ? `$${formatUnits(diagYtPrice, 18)}` : 'N/A'}
+
合约.assetPrice: {diagAssetPrice !== undefined ? `$${formatUnits(diagAssetPrice, 30)}` : 'N/A'}
+
lastPrice: {diagLastPrice !== undefined ? `$${formatUnits(diagLastPrice, 30)}` : 'N/A'}
+
spread: {diagSpread !== undefined ? `${diagSpread.toString()} bps` : 'N/A'}
+
+ getMinPrice: {diagGetMinPrice !== undefined ? `$${formatUnits(diagGetMinPrice, 30)}` : '调用失败'} +
+
+
+ + {/* getPriceInfo 详情 */} + {diagPriceInfo && ( +
+

getPriceInfo 详情

+
+
currentPrice: ${formatUnits((diagPriceInfo as any)[0], 30)}
+
cachedPrice: ${formatUnits((diagPriceInfo as any)[1], 30)}
+
maxPrice: ${formatUnits((diagPriceInfo as any)[2], 30)}
+
minPrice: ${formatUnits((diagPriceInfo as any)[3], 30)}
+
+ {/* 价格差异检查 */} + {(() => { + const current = (diagPriceInfo as any)[0] as bigint | undefined + const cached = (diagPriceInfo as any)[1] as bigint | undefined + if (current && cached && cached > 0n) { + const diff = current > cached + ? ((current - cached) * BigInt(10000)) / cached + : ((cached - current) * BigInt(10000)) / cached + const diffPercent = Number(diff) / 100 + const isOk = diff <= BigInt(500) + return ( +

+ 价格差异: {diffPercent.toFixed(2)}% {isOk ? '(OK)' : '(超过5%!)'} +

+ ) + } + return null + })()} +
+ )} + + {/* 配置问题诊断 */} + {(() => { + const issues: string[] = [] + if (!diagWhitelisted) issues.push('未加入白名单') + if (!diagTokenDecimals || diagTokenDecimals === 0n) issues.push('decimals未配置') + if (!diagMaxUsdyAmount || diagMaxUsdyAmount === 0n) issues.push('maxUsdyAmount未配置') + if (diagAssetPrice === undefined) issues.push('assetPrice()不存在') + if (diagGetMinPrice === undefined) issues.push('getMinPrice()失败') + if (diagLastPrice !== undefined && diagLastPrice === 0n) issues.push('lastPrice为0') + + if (issues.length > 0) { + return ( +
+

配置问题:

+
    + {issues.map((issue, i) =>
  • {issue}
  • )} +
+
+ ) + } + return ( +

+ 配置检查通过 +

+ ) + })()} +
+ )} +
)} {/* Remove Liquidity Form */} {activeTab === 'remove' && ( -
-

{t('lp.removeLiquidity')}

+
+

{t('lp.removeLiquidity')}

{t('lp.removeLiquidityDesc')}

{getCooldownRemaining() > 0 && ( @@ -1105,6 +1366,9 @@ export function LPPanel() { {token.symbol} - {token.name} ))} + {!POOL_TOKENS.some(t => t.address.toLowerCase() === CONTRACTS.WUSD.toLowerCase()) && ( + + )}
@@ -1163,8 +1427,8 @@ export function LPPanel() { {/* Swap Form */} {activeTab === 'swap' && ( -
-

{t('lp.swapTokens')}

+
+

{t('lp.swapTokens')}

{t('lp.swapDesc')}

@@ -1180,6 +1444,9 @@ export function LPPanel() { {token.symbol} ))} + {!POOL_TOKENS.some(t => t.address.toLowerCase() === CONTRACTS.WUSD.toLowerCase()) && ( + + )}
@@ -1195,6 +1462,9 @@ export function LPPanel() { {token.symbol} ))} + {!POOL_TOKENS.some(t => t.address.toLowerCase() === CONTRACTS.WUSD.toLowerCase()) && ( + + )}
@@ -1233,13 +1503,17 @@ export function LPPanel() { )} {/* 边界测试区域 */} -
-
setShowBoundaryTest(!showBoundaryTest)} style={{ cursor: 'pointer' }}> -

{t('test.boundaryTests')} {showBoundaryTest ? '[-]' : '[+]'}

+
+
setShowBoundaryTest(!showBoundaryTest)} + > +

{t('test.boundaryTests')}

+ {showBoundaryTest ? '▼' : '▶'}
{showBoundaryTest && ( - <> +
{t('lp.boundaryHint')}
{/* 冷却时间状态 */} @@ -1253,360 +1527,351 @@ export function LPPanel() {
- {/* 添加流动性边界测试 */}
-
+
{t('lp.testAddZero')} InvalidAmount +

{t('lp.testAddZeroDesc')}

+
+
+
-

{t('lp.testAddZeroDesc')}

-
-
-
+
{t('lp.testAddExceed')} - ERC20InsufficientBalance + InsufficientBalance +

{t('lp.testAddExceedDesc')}

+
+
+
-

{t('lp.testAddExceedDesc')}

-
- - {/* 移除流动性边界测试 */}
-
+
{t('lp.testRemoveZero')} InvalidAmount +

{t('lp.testRemoveZeroDesc')}

+
+
+
-

{t('lp.testRemoveZeroDesc')}

-
-
-
+
{t('lp.testRemoveExceed')} - ERC20InsufficientBalance + InsufficientBalance +

{t('lp.testRemoveExceedDesc')}

+
+
+
-

{t('lp.testRemoveExceedDesc')}

-
-
-
+
{t('lp.testRemoveHighMinout')} InsufficientOutput +

{t('lp.testRemoveHighMinoutDesc')}

+
+
+
-

{t('lp.testRemoveHighMinoutDesc')}

-
- - {/* 代币互换边界测试 */}
-
+
{t('lp.testSwapZero')} InvalidAmount +

{t('lp.testSwapZeroDesc')}

+
+
+
-

{t('lp.testSwapZeroDesc')}

-
-
-
+
{t('lp.testSwapSame')} MaySucceed +

{t('lp.testSwapSameDesc')}

+
+
+
-

{t('lp.testSwapSameDesc')}

-
-
-
+
{t('lp.testSwapExceed')} - ERC20InsufficientBalance + InsufficientBalance +

{t('lp.testSwapExceedDesc')}

+
+
+
-

{t('lp.testSwapExceedDesc')}

-
- +
)}
- {/* 管理员配置区域 - YTPriceFeed */} -
-
setShowAdminConfig(!showAdminConfig)} style={{ cursor: 'pointer' }}> -

管理员配置 (YTPriceFeed) {showAdminConfig ? '[-]' : '[+]'}

+ {/* 管理员配置区域 */} +
+
setShowAdminConfig(!showAdminConfig)} + > +

管理员配置

+ {showAdminConfig ? '▼' : '▶'}
{showAdminConfig && ( - <> -
-

- 注意: 这些是管理员功能,需要 Gov 权限才能执行 -

-

- PriceFeed Gov: {priceFeedGov || '加载中...'} -

-

- 你的地址: {address || '未连接'} -

-

- {address && priceFeedGov && address.toLowerCase() === (priceFeedGov as string).toLowerCase() - ? '✓ 你是管理员,可以执行配置' - : '✗ 你不是管理员,配置可能会失败'} -

+
+ {/* 权限状态 */} +
+
+ YTPriceFeed Gov: {priceFeedGov ? `${(priceFeedGov as string).slice(0, 6)}...${(priceFeedGov as string).slice(-4)}` : '...'} + + {address && priceFeedGov && address.toLowerCase() === (priceFeedGov as string).toLowerCase() ? '[OK]' : '[NO]'} + +
+
+ YTVault Gov: {vaultGov ? `${(vaultGov as string).slice(0, 6)}...${(vaultGov as string).slice(-4)}` : '...'} + + {address && vaultGov && address.toLowerCase() === (vaultGov as string).toLowerCase() ? '[OK]' : '[NO]'} + +
-
-
- - setPriceConfigForm({ ...priceConfigForm, token: e.target.value })} className="input" style={{ fontSize: '12px' }}> + {POOL_TOKENS.map((token) => )}
-
- -
- {selectedTokenPrice !== undefined - ? `$${formatUnits(selectedTokenPrice, 30)}` - : '加载中... (可能未配置)'} + {/* 价格对比 - 关键诊断信息 */} +
+
+ 价格对比 {selectedTokenYtPrice && selectedTokenLastPrice && selectedTokenYtPrice !== selectedTokenLastPrice && '[不匹配!]'} +
+
+
+ ytPrice() [合约]:
+ {selectedTokenYtPrice ? `$${formatUnits(selectedTokenYtPrice as bigint, 30)}` : 'N/A'} +
原始: {selectedTokenYtPrice?.toString()} +
+
+ wusdPrice() [合约]:
+ {selectedTokenWusdPrice ? `$${formatUnits(selectedTokenWusdPrice as bigint, 18)}` : 'N/A'} +
+
+ lastPrice [缓存]:
+ {selectedTokenLastPrice ? `$${formatUnits(selectedTokenLastPrice as bigint, 30)}` : 'N/A'} +
+
+ getMinPrice():
+ + {selectedTokenPrice !== undefined ? `$${formatUnits(selectedTokenPrice, 30)}` : '失败'} + +
+
+ {selectedTokenYtPrice && selectedTokenLastPrice && selectedTokenYtPrice !== selectedTokenLastPrice && ( +
+
价格不匹配导致 getMinPrice() 失败,选择修复方式:
+
+ 方案1: 同步 lastPrice 到 ytPrice + +
+
+ 方案2: 更新 ytPrice (推荐): + setYtPriceForm({ ytPrice: e.target.value })} + placeholder="1.0" + className="input" + style={{ fontSize: '10px', width: '80px', padding: '2px 6px' }} + step="0.01" + /> + + (将 ytPrice 设为 {ytPriceForm.ytPrice}e30) +
+
+ )} +
+ + {/* 当前价差 */} +
+ 当前价差: + {selectedTokenSpread !== undefined ? `${selectedTokenSpread.toString()} bps` : 'N/A'} +
+ + {/* 设置价格 + 设置价差 */} +
+
+ +
+ setPriceConfigForm({ ...priceConfigForm, price: e.target.value })} placeholder="1.0" className="input" style={{ fontSize: '12px', flex: 1 }} step="0.01" /> + +
+
+
+ +
+ setPriceConfigForm({ ...priceConfigForm, spreadBps: e.target.value })} placeholder="20" className="input" style={{ fontSize: '12px', flex: 1 }} /> + +
-
- -
- {selectedTokenSpread !== undefined - ? `${selectedTokenSpread.toString()} bps (${Number(selectedTokenSpread) / 100}%)` - : '加载中...'} + {/* 设置稳定币 + WUSD价格源 */} +
+
+ +
+ + + +
-
-
- -
-

设置价格

-
-
- - setPriceConfigForm({ ...priceConfigForm, price: e.target.value })} - placeholder="1.0" - className="input" - step="0.01" - /> -

- 例如: 1 = $1.00, 1.05 = $1.05 -

-
-
- -
-
- -

设置价差

-
-
- - setPriceConfigForm({ ...priceConfigForm, spreadBps: e.target.value })} - placeholder="20" - className="input" - /> -

- 例如: 20 = 0.2%, 50 = 0.5% -

-
-
- +
+ +
+ setWusdPriceSourceInput(e.target.value)} placeholder="0x..." className="input" style={{ fontSize: '11px', flex: 1 }} /> + +
+

当前: {wusdPriceSource ? (wusdPriceSource as string) : '...'}

{/* YTVault 配置 */} -
-

YTVault 代币配置

-
-

- YTVault Gov: {vaultGov || '加载中...'} -

-

- {address && vaultGov && address.toLowerCase() === (vaultGov as string).toLowerCase() - ? '✓ 你是 YTVault 管理员' - : '✗ 你不是 YTVault 管理员'} -

-
+
+

YTVault 白名单配置

-
-
- + {/* 添加白名单代币 */} +
+
+ + setVaultConfigForm({ ...vaultConfigForm, token: e.target.value })} placeholder="0x..." className="input" style={{ fontSize: '10px', marginTop: '4px' }} />
- -
- - setVaultConfigForm({ ...vaultConfigForm, tokenDecimals: e.target.value })} - placeholder="18" - className="input" - /> -

- 通常为 18 (标准 ERC20) -

+
+ + setVaultConfigForm({ ...vaultConfigForm, tokenDecimals: e.target.value })} placeholder="18" className="input" style={{ fontSize: '12px' }} />
- -
- - setVaultConfigForm({ ...vaultConfigForm, tokenWeight: e.target.value })} - placeholder="10000" - className="input" - /> -

- 权重 (如 10000 = 100%) -

+
+ + setVaultConfigForm({ ...vaultConfigForm, tokenWeight: e.target.value })} placeholder="10000" className="input" style={{ fontSize: '12px' }} />
- -
- - setVaultConfigForm({ ...vaultConfigForm, maxUsdyAmount: e.target.value })} - placeholder="1000000" - className="input" - /> -

- 最大 USDY 限额 (如 1000000) -

+
+ + setVaultConfigForm({ ...vaultConfigForm, maxUsdyAmount: e.target.value })} placeholder="1000000" className="input" style={{ fontSize: '12px' }} />
- - -
- {/* WUSD 价格源配置 */} -
-

YTPriceFeed WUSD 价格源配置

-
-

- 当前 wusdPriceSource: {wusdPriceSource || '加载中...'} -

-

- 配置的 WUSD 地址: {priceFeedWusdAddress || '加载中...'} -

-

- {wusdPriceSource && CONTRACTS.WUSD.toLowerCase() === (wusdPriceSource as string).toLowerCase() - ? '✓ wusdPriceSource 配置正确' - : '✗ wusdPriceSource 配置可能有误,应该设置为 WUSD 合约地址'} -

-
- -
-
- - setWusdPriceSourceInput(e.target.value)} - placeholder={CONTRACTS.WUSD} - className="input" - style={{ fontSize: '12px' }} - /> -

- 通常应设置为 WUSD 合约地址: {CONTRACTS.WUSD} -

-
-
-
+ + {/* 紧急模式 + Swap 开关 */} +
+
+ +
+ + {emergencyMode ? 'ON' : '正常'} + + +
+
+
+ +
+ + {swapEnabled ? '开启' : '关闭'} + + +
+
+
-
-

完整配置步骤:

-
    -
  1. YTPriceFeed: 设置 wusdPriceSource 为 WUSD 合约地址
  2. -
  3. YTPriceFeed: 设置价格 (forceUpdatePrice)
  4. -
  5. YTPriceFeed: 设置价差 (setSpreadBasisPoints) - 可选
  6. -
  7. YTVault: 设置白名单代币 (setWhitelistedToken) - 包含 decimals, weight, maxUsdyAmount
  8. -
  9. 配置完成后,刷新页面查看池子状态
  10. -
-

- 重要: YT代币合约必须实现 assetPrice() 接口 (30位精度),供 YTPriceFeed 读取价格! -

-

- 注意: 如果诊断显示 "assetPrice 不存在",说明 YT 合约没有实现该接口,需要升级合约 + {/* 配置说明 */} +

+ 配置步骤: + 1. 设置价格 → 2. 设置价差(可选) → 3. 添加白名单 +

+ 注意: YT代币需实现 assetPrice() 接口(30位精度)

- +
)}
diff --git a/frontend/src/components/VaultPanel.tsx b/frontend/src/components/VaultPanel.tsx index eb839e2..9cce6c8 100644 --- a/frontend/src/components/VaultPanel.tsx +++ b/frontend/src/components/VaultPanel.tsx @@ -29,6 +29,19 @@ export function VaultPanel() { const [testResult, setTestResult] = useState<{ type: 'success' | 'error' | 'pending', msg: string } | null>(null) const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null) + // 管理员配置状态 + const [showAdminConfig, setShowAdminConfig] = useState(false) + const [priceUpdateForm, setPriceUpdateForm] = useState({ wusdPrice: '1', ytPrice: '1' }) + const [hardCapForm, setHardCapForm] = useState('') + const [redemptionTimeForm, setRedemptionTimeForm] = useState('') + const [managerForm, setManagerForm] = useState('') + + // Manager 资产管理状态 + const [showManagerPanel, setShowManagerPanel] = useState(false) + const [depositManagedAmount, setDepositManagedAmount] = useState('') + const [withdrawManagedAmount, setWithdrawManagedAmount] = useState('') + const [withdrawToAddress, setWithdrawToAddress] = useState('') + // 从合约动态读取所有金库地址 const { data: allVaultsData } = useReadContract({ address: CONTRACTS.FACTORY, @@ -139,6 +152,21 @@ export function VaultPanel() { functionName: 'manager', }) + // 读取 managedAssets (Manager 管理的资产) + const { data: managedAssets, refetch: refetchManagedAssets } = useReadContract({ + address: selectedVault.address as `0x${string}`, + abi: VAULT_ABI, + functionName: 'managedAssets', + }) + + // 读取 Manager 对 Vault 的 WUSD 授权额度 (用于 depositManagedAssets) + const { data: managerWusdAllowance, refetch: refetchManagerAllowance } = useReadContract({ + address: CONTRACTS.WUSD, + abi: WUSD_ABI, + functionName: 'allowance', + args: address ? [address, selectedVault.address as `0x${string}`] : undefined, + }) + const { data: factoryOwner } = useReadContract({ address: CONTRACTS.FACTORY, abi: FACTORY_ABI, @@ -192,8 +220,12 @@ export function VaultPanel() { refetchYtBalance() refetchWusdBalance() refetchAllowance() + refetchManagedAssets() + refetchManagerAllowance() setBuyAmount('') setSellAmount('') + setDepositManagedAmount('') + setWithdrawManagedAmount('') reset() } }, [isSuccess]) @@ -333,6 +365,125 @@ export function VaultPanel() { }) } + // ===== 管理员功能 ===== + + // 更新金库价格 (Factory.updateVaultPrices) + const handleUpdatePrices = () => { + if (!address) return + const wusdPrice = parseUnits(priceUpdateForm.wusdPrice, 18) + const ytPrice = parseUnits(priceUpdateForm.ytPrice, 18) + recordTx('test', `${priceUpdateForm.wusdPrice}/${priceUpdateForm.ytPrice}`, 'UpdatePrices') + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'updateVaultPrices', + args: [selectedVault.address as `0x${string}`, wusdPrice, ytPrice], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置硬顶 (Factory.setHardCap) + const handleSetHardCap = () => { + if (!address || !hardCapForm) return + const hardCap = parseUnits(hardCapForm, 18) + recordTx('test', hardCapForm, 'SetHardCap') + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setHardCap', + args: [selectedVault.address as `0x${string}`, hardCap], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置赎回时间 (Factory.setVaultNextRedemptionTime) + const handleSetRedemptionTime = () => { + if (!address || !redemptionTimeForm) return + const timestamp = BigInt(Math.floor(new Date(redemptionTimeForm).getTime() / 1000)) + recordTx('test', redemptionTimeForm, 'SetRedemptionTime') + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setVaultNextRedemptionTime', + args: [selectedVault.address as `0x${string}`, timestamp], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 设置管理员 (Factory.setVaultManager) + const handleSetManager = () => { + if (!address || !managerForm) return + recordTx('test', managerForm.slice(0, 10) + '...', 'SetManager') + writeContract({ + address: CONTRACTS.FACTORY, + abi: FACTORY_ABI, + functionName: 'setVaultManager', + args: [selectedVault.address as `0x${string}`, managerForm as `0x${string}`], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // ===== Manager 资产管理功能 ===== + + // Manager 授权 WUSD 给 Vault (用于 depositManagedAssets) + const handleManagerApproveWusd = () => { + if (!address || !depositManagedAmount) return + recordTx('approve', depositManagedAmount, 'WUSD') + writeContract({ + address: CONTRACTS.WUSD, + abi: WUSD_ABI, + functionName: 'approve', + args: [selectedVault.address as `0x${string}`, parseUnits(depositManagedAmount, 18)], + gas: GAS_CONFIG.SIMPLE, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 存入托管资产 (Manager 将资产从外部存回金库) + const handleDepositManagedAssets = () => { + if (!address || !depositManagedAmount) return + recordTx('test', depositManagedAmount, 'DepositManaged') + writeContract({ + address: selectedVault.address as `0x${string}`, + abi: VAULT_ABI, + functionName: 'depositManagedAssets', + args: [parseUnits(depositManagedAmount, 18)], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + // 检查 Manager 是否需要授权 + const managerNeedsApproval = depositManagedAmount + ? (managerWusdAllowance === undefined || parseUnits(depositManagedAmount, 18) > managerWusdAllowance) + : false + + // 提取托管资产 (Manager 从金库提取资产进行管理) + const handleWithdrawForManagement = () => { + if (!address || !withdrawManagedAmount) return + const toAddress = withdrawToAddress || address + recordTx('test', withdrawManagedAmount, 'WithdrawManaged') + writeContract({ + address: selectedVault.address as `0x${string}`, + abi: VAULT_ABI, + functionName: 'withdrawForManagement', + args: [toAddress as `0x${string}`, parseUnits(withdrawManagedAmount, 18)], + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + // 边界测试是否正在运行 const [isTestRunning, setIsTestRunning] = useState(false) const testTypeRef = useRef(null) @@ -459,8 +610,9 @@ export function VaultPanel() { } }, [isSuccess, isTestRunning]) - const needsApproval = buyAmount && wusdAllowance !== undefined - ? parseUnits(buyAmount, 18) > wusdAllowance + // 修复: 当 wusdAllowance 未加载时,假设需要授权 (安全起见) + const needsApproval = buyAmount + ? (wusdAllowance === undefined || parseUnits(buyAmount, 18) > wusdAllowance) : false // 格式化剩余时间 @@ -611,6 +763,11 @@ export function VaultPanel() { {t('vault.youWillReceive')}: {formatUnits(previewBuyAmount, 18)} YT
)} + {/* Debug: 显示授权状态 */} +
+ 授权额度: {wusdAllowance !== undefined ? formatUnits(wusdAllowance, 18) : 'loading...'} WUSD + {buyAmount && ` | 需要: ${buyAmount} | 需授权: ${needsApproval ? '是' : '否'}`} +
{needsApproval ? ( +
+
+ + {/* 设置硬顶 */} +

{t('vault.setHardCap')}

+
+
+ + setHardCapForm(e.target.value)} + placeholder="100000" + className="input" + /> +
+
+ +
+
+ + {/* 设置赎回时间 */} +

{t('vault.setRedemptionTime')}

+
+
+ + setRedemptionTimeForm(e.target.value)} + className="input" + /> +
+
+ +
+
+ + {/* 设置管理员 */} +

{t('vault.setManager')}

+
+
+ + setManagerForm(e.target.value)} + placeholder="0x..." + className="input" + /> +
+
+ +
+
+
+ )} +
+ + {/* Manager 资产管理区域 */} +
+
setShowManagerPanel(!showManagerPanel)} + > +

{t('vault.managerPanel')}

+ {showManagerPanel ? '▼' : '▶'} +
+ + {showManagerPanel && ( +
+ {/* 当前托管资产信息 */} +
+
+ {t('vault.managedAssets')}: + {managedAssets ? formatUnits(managedAssets, 18) : '0'} WUSD +
+
+ {t('vault.idleAssets')}: + {vaultInfo ? formatUnits(vaultInfo[1], 18) : '0'} WUSD +
+
+ {address && vaultManager && address.toLowerCase() === vaultManager.toLowerCase() + ? t('vault.youAreManager') + : t('vault.youAreNotManager')} +
+
+ + {/* 存入托管资产 */} +

{t('vault.depositManagedAssets')}

+
+ setDepositManagedAmount(e.target.value)} + placeholder={t('common.amount')} + className="input" + style={{ marginBottom: '8px' }} + /> + {/* 授权状态提示 */} +
+ {t('vault.approvedAmount')}: {managerWusdAllowance !== undefined ? formatUnits(managerWusdAllowance, 18) : '...'} WUSD + {depositManagedAmount && ` | ${t('vault.needApprove')}: ${managerNeedsApproval ? t('test.yes') : t('test.no')}`} +
+
+ {managerNeedsApproval ? ( + + ) : ( + + )} +
+
+ + {/* 提取托管资产 */} +

{t('vault.withdrawForManagement')}

+
+ setWithdrawManagedAmount(e.target.value)} + placeholder={t('common.amount')} + className="input" + style={{ marginBottom: '8px' }} + /> + setWithdrawToAddress(e.target.value)} + placeholder={t('vault.toAddress') + ' (' + t('vault.defaultSelf') + ')'} + className="input" + /> +
+ +
+ )} +
+ {/* 边界测试区域 */} -
-
setShowBoundaryTest(!showBoundaryTest)} style={{ cursor: 'pointer' }}> -

{t('test.boundaryTests')} {showBoundaryTest ? '[-]' : '[+]'}

+
+
setShowBoundaryTest(!showBoundaryTest)} + > +

{t('test.boundaryTests')}

+ {showBoundaryTest ? '▼' : '▶'}
{showBoundaryTest && ( - <> +
{t('test.boundaryHint')}
{/* 赎回状态 */} @@ -692,76 +1065,75 @@ export function VaultPanel() {
-
+
{t('test.buyZero')} InvalidAmount +

{t('test.buyZeroDesc')}

+
+
+
-

{t('test.buyZeroDesc')}

-
-
-
+
{t('test.sellZero')} InvalidAmount +

{t('test.sellZeroDesc')}

+
+
+
-

{t('test.sellZeroDesc')}

-
-
-
+
{t('test.buyExceedBalance')} InsufficientWUSD +

{t('test.buyExceedBalanceDesc')}

+
+
+
-

{t('test.buyExceedBalanceDesc')}

-
-
-
+
{t('test.sellExceedBalance')} - InsufficientYTA + InsufficientYT +

{t('test.sellExceedBalanceDesc')}

+
+
+
-

{t('test.sellExceedBalanceDesc')}

-
-
-
+
{t('test.buyExceedHardcap')} HardCapExceeded +

{t('test.buyExceedHardcapDesc')}

+
+
+
-

{t('test.buyExceedHardcapDesc')}

-
-
-
+
{t('test.sellInLock')} StillInLockPeriod +

{t('test.sellInLockDesc')}

+
+
+ +
+
+
+
+ {t('test.maxApprove')} + MaxUint256 +

{t('test.maxApproveDesc')}

+
+
+
-

{t('test.sellInLockDesc')}

-
-
- -
-
{testResult && ( @@ -769,7 +1141,7 @@ export function VaultPanel() { {testResult.msg}
)} - +
)}
diff --git a/frontend/src/components/WUSDPanel.tsx b/frontend/src/components/WUSDPanel.tsx index d626557..b974833 100644 --- a/frontend/src/components/WUSDPanel.tsx +++ b/frontend/src/components/WUSDPanel.tsx @@ -14,6 +14,7 @@ export function WUSDPanel() { const { transactions, addTransaction, updateTransaction, clearHistory } = useTransactions() const { showToast } = useToast() const [mintAmount, setMintAmount] = useState('') + const [burnAmount, setBurnAmount] = useState('') const [showBoundaryTest, setShowBoundaryTest] = useState(false) const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null) @@ -161,7 +162,21 @@ export function WUSDPanel() { abi: WUSD_ABI, functionName: 'mint', args: [address, parseUnits(mintAmount, decimals)], - gas: GAS_CONFIG.SIMPLE, + gas: GAS_CONFIG.STANDARD, + maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, + maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, + }) + } + + const handleBurn = async () => { + if (!address || !burnAmount || !decimals) return + recordTx('burn', burnAmount, 'WUSD') + writeContract({ + address: CONTRACTS.WUSD, + abi: WUSD_ABI, + functionName: 'burn', + args: [address, parseUnits(burnAmount, decimals)], + gas: GAS_CONFIG.STANDARD, maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, }) @@ -178,7 +193,7 @@ export function WUSDPanel() { abi: WUSD_ABI, functionName: 'mint', args: [address, BigInt(0)], - gas: GAS_CONFIG.SIMPLE, + gas: GAS_CONFIG.STANDARD, maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, }) @@ -190,7 +205,7 @@ export function WUSDPanel() { abi: WUSD_ABI, functionName: 'burn', args: [address, exceedAmount], - gas: GAS_CONFIG.SIMPLE, + gas: GAS_CONFIG.STANDARD, maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, }) @@ -202,7 +217,7 @@ export function WUSDPanel() { abi: WUSD_ABI, functionName: 'mint', args: [address, parseUnits('10000', decimals)], - gas: GAS_CONFIG.SIMPLE, + gas: GAS_CONFIG.STANDARD, maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS, maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS, }) @@ -235,65 +250,101 @@ export function WUSDPanel() {
-
- - setMintAmount(e.target.value)} - placeholder={t('wusd.enterAmount')} - className="input" - /> +
+ {/* 铸造 */} +
+
+ + setMintAmount(e.target.value)} + placeholder={t('wusd.enterAmount')} + className="input" + /> +
+ +
+ + {/* 销毁 */} +
+
+ + setBurnAmount(e.target.value)} + placeholder={t('wusd.enterAmount')} + className="input" + /> +
+ +
- - {/* 边界测试区域 */} -
-
setShowBoundaryTest(!showBoundaryTest)} style={{ cursor: 'pointer' }}> -

{t('test.boundaryTests')} {showBoundaryTest ? '[-]' : '[+]'}

+
+
setShowBoundaryTest(!showBoundaryTest)} + > +

{t('test.boundaryTests')}

+ {showBoundaryTest ? '▼' : '▶'}
{showBoundaryTest && ( - <> +
{t('test.boundaryHint')}
-
+
{t('test.mintZero')} MaySucceed +

{t('test.mintZeroDesc')}

+
+
+
-

{t('test.mintZeroDesc')}

-
-
+
{t('test.burnExceed')} - ERC20InsufficientBalance + InsufficientBalance +

{t('test.burnExceedDesc')}

+
+
+ +
+
+ +
+
+ {t('test.mint10000')} + Mint +

{t('test.mint10000Desc')}

+
+
+
-

{t('test.burnExceedDesc')}

-
- -
- -
- +
)}
diff --git a/frontend/src/config/contracts.ts b/frontend/src/config/contracts.ts index 6c524f3..04eed60 100644 --- a/frontend/src/config/contracts.ts +++ b/frontend/src/config/contracts.ts @@ -49,6 +49,7 @@ export const CONTRACTS = { YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as const, YT_B: '0x333805C9EE75f59Aa2Cc79DfDe2499F920c7b408' as const, YT_C: '0x6DF0ED6f0345F601A206974973dE9fC970598587' as const, + YT_D: '0x5d91FD16fa85547b0784c377A47BF7706D7875d3' as const, }, // LP Pool contracts YT_REWARD_ROUTER: '0x51eEF57eC57c867AC23945f0ce21aA5A9a2C246c' as const, @@ -189,6 +190,81 @@ export const FACTORY_ABI = [ outputs: [{ internalType: 'address', name: '', type: 'address' }], stateMutability: 'view', type: 'function' + }, + // 批量获取金库(分页) + { + inputs: [ + { internalType: 'uint256', name: '_start', type: 'uint256' }, + { internalType: 'uint256', name: '_end', type: 'uint256' } + ], + name: 'getVaults', + outputs: [{ internalType: 'address[]', name: 'vaults', type: 'address[]' }], + stateMutability: 'view', + type: 'function' + }, + // 批量设置硬顶 + { + inputs: [ + { internalType: 'address[]', name: '_vaults', type: 'address[]' }, + { internalType: 'uint256[]', name: '_hardCaps', type: 'uint256[]' } + ], + name: 'setHardCapBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 批量更新价格 + { + inputs: [ + { internalType: 'address[]', name: '_vaults', type: 'address[]' }, + { internalType: 'uint256[]', name: '_wusdPrices', type: 'uint256[]' }, + { internalType: 'uint256[]', name: '_ytPrices', type: 'uint256[]' } + ], + name: 'updateVaultPricesBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 批量设置赎回时间 + { + inputs: [ + { internalType: 'address[]', name: '_vaults', type: 'address[]' }, + { internalType: 'uint256', name: '_nextRedemptionTime', type: 'uint256' } + ], + name: 'setVaultNextRedemptionTimeBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 设置金库实现地址 + { + inputs: [{ internalType: 'address', name: '_newImplementation', type: 'address' }], + name: 'setVaultImplementation', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 升级单个金库 + { + inputs: [ + { internalType: 'address', name: '_vault', type: 'address' }, + { internalType: 'address', name: '_newImplementation', type: 'address' } + ], + name: 'upgradeVault', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 批量升级金库 + { + inputs: [ + { internalType: 'address[]', name: '_vaults', type: 'address[]' }, + { internalType: 'address', name: '_newImplementation', type: 'address' } + ], + name: 'upgradeVaultBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' } ] as const @@ -974,5 +1050,120 @@ export const YT_VAULT_ABI = [ outputs: [], stateMutability: 'nonpayable', type: 'function' + }, + // 获取交换手续费率 - 用于滑点计算 + { + inputs: [ + { internalType: 'address', name: '_tokenIn', type: 'address' }, + { internalType: 'address', name: '_tokenOut', type: 'address' }, + { internalType: 'uint256', name: '_usdyAmount', type: 'uint256' } + ], + name: 'getSwapFeeBasisPoints', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + // 获取赎回手续费率 - 用于移除流动性滑点计算 + { + inputs: [ + { internalType: 'address', name: '_token', type: 'address' }, + { internalType: 'uint256', name: '_usdyAmount', type: 'uint256' } + ], + name: 'getRedemptionFeeBasisPoints', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + // 获取动态手续费率 + { + inputs: [ + { internalType: 'address', name: '_token', type: 'address' }, + { internalType: 'uint256', name: '_usdyDelta', type: 'uint256' }, + { internalType: 'uint256', name: '_feeBasisPoints', type: 'uint256' }, + { internalType: 'uint256', name: '_taxBasisPoints', type: 'uint256' }, + { internalType: 'bool', name: '_increment', type: 'bool' } + ], + name: 'getFeeBasisPoints', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + // 获取池子总价值 + { + inputs: [{ internalType: 'bool', name: '_maximise', type: 'bool' }], + name: 'getPoolValue', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + // 获取所有池子代币 + { + inputs: [], + name: 'getAllPoolTokens', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function' + }, + // 常量 + { + inputs: [], + name: 'BASIS_POINTS_DIVISOR', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'PRICE_PRECISION', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'USDY_DECIMALS', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + // 紧急模式 + { + inputs: [], + name: 'emergencyMode', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + // swap 开关 + { + inputs: [], + name: 'swapEnabled', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + // 设置紧急模式 + { + inputs: [{ internalType: 'bool', name: '_emergencyMode', type: 'bool' }], + name: 'setEmergencyMode', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 设置 swap 开关 + { + inputs: [{ internalType: 'bool', name: '_isSwapEnabled', type: 'bool' }], + name: 'setSwapEnabled', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + // 稳定币标记 + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'stableTokens', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' } ] as const diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 3664be1..bdf9cc7 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -33,8 +33,10 @@ "wusd": { "title": "WUSD Token", "mintAmount": "Mint Amount", - "enterAmount": "Enter amount to mint", - "mint": "Mint WUSD", + "burnAmount": "Burn Amount", + "enterAmount": "Enter amount", + "mint": "Mint", + "burn": "Burn", "minting": "Minting...", "confirming": "Confirming...", "mintSuccess": "Mint successful!" @@ -65,12 +67,33 @@ "redeemAvailable": "Available", "redeemNotAvailable": "Not Available", "redeemLocked": "Redemption Locked", - "timeRemaining": "Time Remaining" + "timeRemaining": "Time Remaining", + "adminConfig": "Admin Config", + "updatePrices": "Update Prices", + "setHardCap": "Set Hard Cap", + "setRedemptionTime": "Set Redemption Time", + "setManager": "Set Manager", + "newHardCap": "New Hard Cap", + "newManager": "New Manager Address", + "newRedemptionTime": "New Redemption Time", + "managerPanel": "Manager Panel", + "managedAssets": "Managed Assets", + "youAreManager": "You are the Manager of this vault", + "youAreNotManager": "You are not the Manager of this vault", + "depositManagedAssets": "Deposit Managed Assets", + "withdrawForManagement": "Withdraw For Management", + "deposit": "Deposit", + "withdraw": "Withdraw", + "toAddress": "To Address", + "defaultSelf": "default: self", + "approvedAmount": "Approved Amount", + "needApprove": "Need Approve" }, "factory": { "title": "Factory Management", "factoryContract": "Factory Contract", "owner": "Owner", + "vaultImpl": "Vault Implementation", "defaultHardCap": "Default Hard Cap", "totalVaults": "Total Vaults", "yourRole": "Your Role", @@ -91,7 +114,27 @@ "selectVault": "Select Vault", "newWusdPrice": "New WUSD Price", "newYtPrice": "New YT Price", - "update": "Update Prices" + "update": "Update Prices", + "ownerConfig": "Owner Config", + "setDefaultHardCap": "Set Default Hard Cap", + "batchOperations": "Batch Operations", + "selectVaultsForBatch": "Select Vaults", + "selectAll": "Select All", + "deselectAll": "Deselect All", + "selectedCount": "Selected", + "batchUpdatePrices": "Batch Update Prices", + "batchSetHardCap": "Batch Set Hard Cap", + "batchSetRedemptionTime": "Batch Set Redemption Time", + "advancedFunctions": "Advanced Functions", + "advancedWarning": "Warning: These are advanced management functions. Please ensure you understand the implications before proceeding. Incorrect operations may cause contract issues.", + "setVaultImplementation": "Set Vault Implementation", + "currentImpl": "Current Implementation", + "upgradeVault": "Upgrade Vault", + "newImplAddress": "New Implementation Address", + "upgrade": "Upgrade", + "batchUpgradeVault": "Batch Upgrade Vaults", + "batchUpgrade": "Batch Upgrade", + "selectInBatchSection": "select in batch section" }, "language": { "en": "English", @@ -136,7 +179,9 @@ "run": "Run", "running": "Running...", "mint10000": "Mint 10000 WUSD", + "mint10000Desc": "Quick mint 10000 test WUSD", "maxApprove": "Max Approve WUSD", + "maxApproveDesc": "Approve max uint256 amount", "buyZero": "Buy Amount 0", "buyZeroDesc": "Test depositYT(0)", "sellZero": "Sell Amount 0", @@ -207,6 +252,18 @@ "testSwapExceedDesc": "Swap amount exceeding token balance", "yourTokenBalances": "Your Token Balances", "whitelisted": "Whitelisted", - "notWhitelisted": "Not Whitelisted" + "notWhitelisted": "Not Whitelisted", + "emergencyMode": "Emergency Mode", + "emergencyModeOn": "ON (Warning)", + "emergencyModeOff": "Normal", + "swapEnabled": "Swap Function", + "swapEnabledOn": "Enabled", + "swapEnabledOff": "Disabled", + "totalTokenWeights": "Total Weights", + "accountValue": "Your Account Value", + "usdyAmount": "USDY Amount", + "isStableToken": "Stable Token", + "setStableToken": "Set Stable Token", + "stableTokenHint": "Mark token as stable, affects price calculation" } } diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index 1c9225c..4752e37 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -33,8 +33,10 @@ "wusd": { "title": "WUSD 代币", "mintAmount": "铸造数量", - "enterAmount": "输入铸造数量", - "mint": "铸造 WUSD", + "burnAmount": "销毁数量", + "enterAmount": "输入数量", + "mint": "铸造", + "burn": "销毁", "minting": "铸造中...", "confirming": "确认中...", "mintSuccess": "铸造成功!" @@ -65,12 +67,33 @@ "redeemAvailable": "可以赎回", "redeemNotAvailable": "暂不可赎回", "redeemLocked": "赎回锁定中", - "timeRemaining": "剩余时间" + "timeRemaining": "剩余时间", + "adminConfig": "管理员配置", + "updatePrices": "更新价格", + "setHardCap": "设置硬顶", + "setRedemptionTime": "设置赎回时间", + "setManager": "设置管理员", + "newHardCap": "新硬顶", + "newManager": "新管理员地址", + "newRedemptionTime": "新赎回时间", + "managerPanel": "Manager 面板", + "managedAssets": "托管资产", + "youAreManager": "你是当前金库的 Manager", + "youAreNotManager": "你不是当前金库的 Manager", + "depositManagedAssets": "存入托管资产", + "withdrawForManagement": "提取托管资产", + "deposit": "存入", + "withdraw": "提取", + "toAddress": "接收地址", + "defaultSelf": "默认为自己", + "approvedAmount": "已授权额度", + "needApprove": "需要授权" }, "factory": { "title": "工厂管理", "factoryContract": "工厂合约", "owner": "所有者", + "vaultImpl": "金库实现", "defaultHardCap": "默认硬顶", "totalVaults": "金库总数", "yourRole": "你的角色", @@ -91,7 +114,27 @@ "selectVault": "选择金库", "newWusdPrice": "新 WUSD 价格", "newYtPrice": "新 YT 价格", - "update": "更新价格" + "update": "更新价格", + "ownerConfig": "Owner 配置", + "setDefaultHardCap": "设置默认硬顶", + "batchOperations": "批量操作", + "selectVaultsForBatch": "选择金库", + "selectAll": "全选", + "deselectAll": "取消全选", + "selectedCount": "已选择", + "batchUpdatePrices": "批量更新价格", + "batchSetHardCap": "批量设置硬顶", + "batchSetRedemptionTime": "批量设置赎回时间", + "advancedFunctions": "高级功能", + "advancedWarning": "警告: 以下是高级管理功能,操作前请确保了解其影响。错误的操作可能导致合约异常。", + "setVaultImplementation": "设置金库实现", + "currentImpl": "当前实现", + "upgradeVault": "升级金库", + "newImplAddress": "新实现地址", + "upgrade": "升级", + "batchUpgradeVault": "批量升级金库", + "batchUpgrade": "批量升级", + "selectInBatchSection": "在批量操作区选择" }, "language": { "en": "英文", @@ -136,7 +179,9 @@ "run": "执行", "running": "执行中...", "mint10000": "铸造 10000 WUSD", + "mint10000Desc": "快速铸造10000个测试WUSD", "maxApprove": "最大授权 WUSD", + "maxApproveDesc": "授权最大uint256额度", "buyZero": "买入金额为0", "buyZeroDesc": "测试 depositYT(0)", "sellZero": "卖出金额为0", @@ -207,6 +252,18 @@ "testSwapExceedDesc": "互换金额超过代币余额", "yourTokenBalances": "你的代币余额", "whitelisted": "已白名单", - "notWhitelisted": "未白名单" + "notWhitelisted": "未白名单", + "emergencyMode": "紧急模式", + "emergencyModeOn": "已开启 (警告)", + "emergencyModeOff": "正常", + "swapEnabled": "交换功能", + "swapEnabledOn": "已开启", + "swapEnabledOff": "已关闭", + "totalTokenWeights": "总权重", + "accountValue": "你的账户价值", + "usdyAmount": "USDY 数量", + "isStableToken": "稳定币", + "setStableToken": "设置稳定币", + "stableTokenHint": "将代币标记为稳定币,影响价格计算方式" } }