feat: 添加排队退出机制和样式优化
新功能 - 排队退出机制: - 用户卖出 YT 改为申请退出,创建请求加入队列 - 显示队列进度信息(总请求数/待处理/已处理) - 显示用户待处理请求列表 - 管理员配置区域添加批量处理退出功能 合约 ABI 更新: - withdrawYT 返回 requestId - getUserPendingRequests 获取用户待处理请求 - getQueueProgress 获取队列进度 - processBatchWithdrawals 批量处理退出 样式优化: - LP 流动池页面彩色背景改为白灰色 - 金库交易页面硬顶 Swap 区域样式统一 - 硬顶判断条件修改为使用 totalSupply 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -320,7 +320,7 @@ body {
|
||||
|
||||
/* Balance Info */
|
||||
.balance-info {
|
||||
background: #e3f2fd;
|
||||
background: #f8f9fa;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
@@ -907,11 +907,11 @@ body {
|
||||
|
||||
/* LP Panel Styles */
|
||||
.pool-info {
|
||||
background: #f0f7ff;
|
||||
background: #f8f9fa;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #bbdefb;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.lp-tabs {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, 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, GAS_CONFIG, FACTORY_ABI } from '../config/contracts'
|
||||
import { CONTRACTS, GAS_CONFIG, FACTORY_ABI, VAULT_ABI } from '../config/contracts'
|
||||
|
||||
export function FactoryPanel() {
|
||||
const { t } = useTranslation()
|
||||
@@ -33,6 +33,19 @@ export function FactoryPanel() {
|
||||
const [upgradeVaultAddr, setUpgradeVaultAddr] = useState('')
|
||||
const [upgradeImplAddr, setUpgradeImplAddr] = useState('')
|
||||
const [batchRedemptionTime, setBatchRedemptionTime] = useState('')
|
||||
const [checkVaultAddr, setCheckVaultAddr] = useState('')
|
||||
const [showBatchCreate, setShowBatchCreate] = useState(false)
|
||||
const [batchCreateVaults, setBatchCreateVaults] = useState<{
|
||||
name: string
|
||||
symbol: string
|
||||
manager: string
|
||||
hardCap: string
|
||||
redemptionTime: string
|
||||
wusdPrice: string
|
||||
ytPrice: string
|
||||
}[]>([
|
||||
{ name: '', symbol: '', manager: '', hardCap: '100000', redemptionTime: '', wusdPrice: '1', ytPrice: '1' }
|
||||
])
|
||||
|
||||
const { data: allVaults, refetch: refetchVaults } = useReadContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
@@ -40,6 +53,32 @@ export function FactoryPanel() {
|
||||
functionName: 'getAllVaults',
|
||||
})
|
||||
|
||||
// 批量读取所有金库的暂停状态
|
||||
const pausedContracts = useMemo(() => {
|
||||
if (!allVaults || allVaults.length === 0) return []
|
||||
return allVaults.map((addr: string) => ({
|
||||
address: addr as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'paused' as const,
|
||||
}))
|
||||
}, [allVaults])
|
||||
|
||||
const { data: pausedResults, refetch: refetchPaused } = useReadContracts({
|
||||
contracts: pausedContracts,
|
||||
})
|
||||
|
||||
// 构建金库暂停状态映射
|
||||
const vaultPausedMap = useMemo(() => {
|
||||
const map: Record<string, boolean> = {}
|
||||
if (allVaults && pausedResults) {
|
||||
allVaults.forEach((addr: string, i: number) => {
|
||||
const result = pausedResults[i]
|
||||
map[addr.toLowerCase()] = result?.status === 'success' ? (result.result as boolean) : false
|
||||
})
|
||||
}
|
||||
return map
|
||||
}, [allVaults, pausedResults])
|
||||
|
||||
const { data: vaultCount } = useReadContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
@@ -64,6 +103,16 @@ export function FactoryPanel() {
|
||||
functionName: 'vaultImplementation',
|
||||
})
|
||||
|
||||
// 验证地址是否为有效金库
|
||||
const { data: isValidVault } = useReadContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'isVault',
|
||||
args: checkVaultAddr && checkVaultAddr.startsWith('0x') && checkVaultAddr.length === 42
|
||||
? [checkVaultAddr as `0x${string}`]
|
||||
: undefined,
|
||||
})
|
||||
|
||||
const { writeContract, data: hash, isPending, reset, error: writeError, status: writeStatus } = useWriteContract()
|
||||
|
||||
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
|
||||
@@ -73,6 +122,7 @@ export function FactoryPanel() {
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
refetchVaults()
|
||||
refetchPaused()
|
||||
refetchDefaultHardCap()
|
||||
reset()
|
||||
}
|
||||
@@ -101,8 +151,8 @@ export function FactoryPanel() {
|
||||
parseUnits(createForm.hardCap, 18),
|
||||
CONTRACTS.WUSD,
|
||||
BigInt(redemptionTimestamp),
|
||||
parseUnits(createForm.initialWusdPrice, 18),
|
||||
parseUnits(createForm.initialYtPrice, 18),
|
||||
parseUnits(createForm.initialWusdPrice, 30), // wusdPrice 使用 30 位精度
|
||||
parseUnits(createForm.initialYtPrice, 30), // ytPrice 使用 30 位精度
|
||||
],
|
||||
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
@@ -117,8 +167,8 @@ export function FactoryPanel() {
|
||||
functionName: 'updateVaultPrices',
|
||||
args: [
|
||||
priceForm.vault as `0x${string}`,
|
||||
parseUnits(priceForm.wusdPrice, 18),
|
||||
parseUnits(priceForm.ytPrice, 18),
|
||||
parseUnits(priceForm.wusdPrice, 30), // wusdPrice 使用 30 位精度
|
||||
parseUnits(priceForm.ytPrice, 30), // ytPrice 使用 30 位精度
|
||||
],
|
||||
gas: GAS_CONFIG.STANDARD,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
@@ -142,8 +192,8 @@ export function FactoryPanel() {
|
||||
// 批量更新价格
|
||||
const handleBatchUpdatePrices = () => {
|
||||
if (selectedVaultsForBatch.length === 0) return
|
||||
const wusdPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.wusdPrice, 18))
|
||||
const ytPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.ytPrice, 18))
|
||||
const wusdPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.wusdPrice, 30)) // wusdPrice 使用 30 位精度
|
||||
const ytPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.ytPrice, 30)) // ytPrice 使用 30 位精度
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
@@ -203,6 +253,104 @@ export function FactoryPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
// 暂停单个金库
|
||||
const handlePauseVault = (vaultAddr: string) => {
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'pauseVault',
|
||||
args: [vaultAddr as `0x${string}`],
|
||||
gas: GAS_CONFIG.STANDARD,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 恢复单个金库
|
||||
const handleUnpauseVault = (vaultAddr: string) => {
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'unpauseVault',
|
||||
args: [vaultAddr as `0x${string}`],
|
||||
gas: GAS_CONFIG.STANDARD,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 批量暂停金库
|
||||
const handleBatchPauseVaults = () => {
|
||||
if (selectedVaultsForBatch.length === 0) return
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'pauseVaultBatch',
|
||||
args: [selectedVaultsForBatch as `0x${string}`[]],
|
||||
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 批量恢复金库
|
||||
const handleBatchUnpauseVaults = () => {
|
||||
if (selectedVaultsForBatch.length === 0) return
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'unpauseVaultBatch',
|
||||
args: [selectedVaultsForBatch as `0x${string}`[]],
|
||||
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 批量创建金库
|
||||
const handleBatchCreateVaults = () => {
|
||||
// 验证所有金库配置
|
||||
const validVaults = batchCreateVaults.filter(v => v.name && v.symbol && v.manager && v.hardCap && v.redemptionTime)
|
||||
if (validVaults.length === 0) return
|
||||
|
||||
const names = validVaults.map(v => v.name)
|
||||
const symbols = validVaults.map(v => v.symbol)
|
||||
const managers = validVaults.map(v => v.manager as `0x${string}`)
|
||||
const hardCaps = validVaults.map(v => parseUnits(v.hardCap, 18))
|
||||
const redemptionTimes = validVaults.map(v => BigInt(Math.floor(new Date(v.redemptionTime).getTime() / 1000)))
|
||||
const wusdPrices = validVaults.map(v => parseUnits(v.wusdPrice || '1', 30))
|
||||
const ytPrices = validVaults.map(v => parseUnits(v.ytPrice || '1', 30))
|
||||
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'createVaultBatch',
|
||||
args: [names, symbols, managers, hardCaps, CONTRACTS.WUSD, redemptionTimes, wusdPrices, ytPrices],
|
||||
gas: GAS_CONFIG.VERY_COMPLEX * BigInt(validVaults.length),
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 添加新的金库配置行
|
||||
const addBatchCreateRow = () => {
|
||||
setBatchCreateVaults([...batchCreateVaults, { name: '', symbol: '', manager: '', hardCap: '100000', redemptionTime: '', wusdPrice: '1', ytPrice: '1' }])
|
||||
}
|
||||
|
||||
// 删除金库配置行
|
||||
const removeBatchCreateRow = (index: number) => {
|
||||
if (batchCreateVaults.length > 1) {
|
||||
setBatchCreateVaults(batchCreateVaults.filter((_, i) => i !== index))
|
||||
}
|
||||
}
|
||||
|
||||
// 更新金库配置行
|
||||
const updateBatchCreateRow = (index: number, field: string, value: string) => {
|
||||
const updated = [...batchCreateVaults]
|
||||
updated[index] = { ...updated[index], [field]: value }
|
||||
setBatchCreateVaults(updated)
|
||||
}
|
||||
|
||||
// 设置金库实现地址
|
||||
const handleSetVaultImplementation = () => {
|
||||
if (!newImplementation) return
|
||||
@@ -255,7 +403,7 @@ export function FactoryPanel() {
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'updateVaultPrices',
|
||||
args: [testVault as `0x${string}`, parseUnits('1', 18), parseUnits('1', 18)],
|
||||
args: [testVault as `0x${string}`, parseUnits('1', 30), parseUnits('1', 30)], // wusdPrice 和 ytPrice 都使用 30 位精度
|
||||
gas: GAS_CONFIG.STANDARD,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
@@ -298,41 +446,73 @@ export function FactoryPanel() {
|
||||
|
||||
<div className="factory-info">
|
||||
<div className="info-row">
|
||||
<span>{t('factory.factoryContract')}:</span>
|
||||
<span title="工厂合约 Factory Contract - 负责创建和管理所有 YT 金库的工厂合约地址" style={{ cursor: 'help' }}>{t('factory.factoryContract')}:</span>
|
||||
<code>{CONTRACTS.FACTORY}</code>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('factory.owner')}:</span>
|
||||
<span title="所有者 Owner - 工厂合约的所有者,拥有创建金库、更新价格等最高权限" style={{ cursor: 'help' }}>{t('factory.owner')}:</span>
|
||||
<code>{owner || '-'}</code>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('factory.vaultImpl')}:</span>
|
||||
<span title="金库实现 Vault Implementation - 金库代理合约的实现地址,用于可升级架构" style={{ cursor: 'help' }}>{t('factory.vaultImpl')}:</span>
|
||||
<code style={{ fontSize: '10px' }}>{vaultImplementation || '-'}</code>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('factory.defaultHardCap')}:</span>
|
||||
<span title="默认硬顶 Default Hard Cap - 新创建金库的默认最大存款额度" style={{ cursor: 'help' }}>{t('factory.defaultHardCap')}:</span>
|
||||
<strong>{defaultHardCap ? formatUnits(defaultHardCap, 18) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('factory.totalVaults')}:</span>
|
||||
<span title="金库总数 Total Vaults - 工厂已创建的金库数量" style={{ cursor: 'help' }}>{t('factory.totalVaults')}:</span>
|
||||
<strong>{vaultCount?.toString() || '0'}</strong>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('factory.yourRole')}:</span>
|
||||
<span title="你的角色 Your Role - 当前连接钱包在工厂合约中的角色" style={{ cursor: 'help' }}>{t('factory.yourRole')}:</span>
|
||||
<strong>{isOwner ? t('factory.roleOwner') : t('factory.roleUser')}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<h3>{t('factory.allVaults')}</h3>
|
||||
<h3 title="所有金库 All Vaults - 工厂创建的所有 YT 资产金库列表" style={{ cursor: 'help' }}>{t('factory.allVaults')}</h3>
|
||||
<div className="vault-list">
|
||||
{allVaults && allVaults.length > 0 ? (
|
||||
allVaults.map((vault, index) => (
|
||||
<div key={index} className="vault-item">
|
||||
<span>Vault {index + 1}:</span>
|
||||
<code>{vault}</code>
|
||||
</div>
|
||||
))
|
||||
allVaults.map((vault, index) => {
|
||||
const isPaused = vaultPausedMap[vault.toLowerCase()] || false
|
||||
return (
|
||||
<div key={index} className="vault-item" style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<span>Vault {index + 1}:</span>
|
||||
<code style={{ flex: 1 }}>{vault}</code>
|
||||
{/* 暂停状态标签 */}
|
||||
<span style={{
|
||||
padding: '2px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '11px',
|
||||
background: isPaused ? '#ffebee' : '#e8f5e9',
|
||||
color: isPaused ? '#c62828' : '#2e7d32',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{isPaused ? t('factory.paused') : t('factory.active')}
|
||||
</span>
|
||||
{isOwner && (
|
||||
<button
|
||||
onClick={() => isPaused ? handleUnpauseVault(vault) : handlePauseVault(vault)}
|
||||
disabled={isProcessing}
|
||||
className="btn btn-sm"
|
||||
style={{
|
||||
padding: '2px 10px',
|
||||
fontSize: '11px',
|
||||
background: isPaused ? '#4caf50' : '#ff9800',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}
|
||||
title={isPaused ? t('factory.unpauseVault') : t('factory.pauseVault')}
|
||||
>
|
||||
{isPaused ? t('factory.unpauseVault') : t('factory.pauseVault')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<p className="text-muted">{t('factory.noVaults')}</p>
|
||||
)}
|
||||
@@ -342,7 +522,7 @@ export function FactoryPanel() {
|
||||
{isOwner && (
|
||||
<>
|
||||
<div className="section">
|
||||
<h3>{t('factory.createVault')}</h3>
|
||||
<h3 title="创建金库 Create Vault - 部署新的 YT 资产金库合约" style={{ cursor: 'help' }}>{t('factory.createVault')}</h3>
|
||||
<div className="form-grid">
|
||||
<div className="form-group">
|
||||
<label>{t('factory.name')}</label>
|
||||
@@ -421,13 +601,142 @@ export function FactoryPanel() {
|
||||
>
|
||||
{isProcessing ? t('common.processing') : t('factory.create')}
|
||||
</button>
|
||||
|
||||
{/* 批量创建金库 - 可折叠 */}
|
||||
<div style={{ marginTop: '16px', padding: '12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowBatchCreate(!showBatchCreate)}
|
||||
>
|
||||
<h4 style={{ margin: 0, fontSize: '13px', color: '#666' }}>{t('factory.batchCreateVaults')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showBatchCreate ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
{showBatchCreate && (
|
||||
<div style={{ marginTop: '12px' }}>
|
||||
{batchCreateVaults.map((vault, index) => (
|
||||
<div key={index} style={{ marginBottom: '12px', padding: '10px', background: '#fff', borderRadius: '6px', border: '1px solid #e0e0e0' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||
<span style={{ fontWeight: 'bold', fontSize: '12px', color: '#333' }}>Vault {index + 1}</span>
|
||||
{batchCreateVaults.length > 1 && (
|
||||
<button
|
||||
onClick={() => removeBatchCreateRow(index)}
|
||||
style={{ padding: '2px 8px', fontSize: '11px', background: '#f44336', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', marginBottom: '8px' }}>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('factory.name')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={vault.name}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'name', e.target.value)}
|
||||
placeholder="YT-X"
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('factory.symbol')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={vault.symbol}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'symbol', e.target.value)}
|
||||
placeholder="YTX"
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('factory.managerAddress')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={vault.manager}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'manager', e.target.value)}
|
||||
placeholder="0x..."
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('vault.hardCap')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={vault.hardCap}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'hardCap', e.target.value)}
|
||||
placeholder="100000"
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px' }}>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('factory.redemptionTime')}</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={vault.redemptionTime}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'redemptionTime', e.target.value)}
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('vault.wusdPrice')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={vault.wusdPrice}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'wusdPrice', e.target.value)}
|
||||
placeholder="1"
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ fontSize: '10px', color: '#666' }}>{t('vault.ytPrice')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={vault.ytPrice}
|
||||
onChange={(e) => updateBatchCreateRow(index, 'ytPrice', e.target.value)}
|
||||
placeholder="1"
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
||||
<button
|
||||
onClick={addBatchCreateRow}
|
||||
className="btn btn-sm"
|
||||
style={{ background: '#1976d2', color: '#fff', border: 'none' }}
|
||||
>
|
||||
+ {t('factory.addVaultRow')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleBatchCreateVaults}
|
||||
disabled={isProcessing || batchCreateVaults.filter(v => v.name && v.symbol && v.manager && v.hardCap && v.redemptionTime).length === 0}
|
||||
className="btn btn-primary btn-sm"
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
{isProcessing ? t('common.processing') : t('factory.createBatch')} ({batchCreateVaults.filter(v => v.name && v.symbol && v.manager && v.hardCap && v.redemptionTime).length})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<h3>{t('factory.updatePrices')}</h3>
|
||||
<h3 title="更新价格 Update Prices - 修改指定金库的 WUSD 和 YT 价格" style={{ cursor: 'help' }}>{t('factory.updatePrices')}</h3>
|
||||
<div className="form-grid">
|
||||
<div className="form-group">
|
||||
<label>{t('factory.vaultAddress')}</label>
|
||||
<label title="金库地址 Vault Address - 选择要更新价格的金库" style={{ cursor: 'help' }}>{t('factory.vaultAddress')}</label>
|
||||
<select
|
||||
value={priceForm.vault}
|
||||
onChange={(e) => setPriceForm({ ...priceForm, vault: e.target.value })}
|
||||
@@ -479,7 +788,7 @@ export function FactoryPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowOwnerConfig(!showOwnerConfig)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('factory.ownerConfig')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="Owner配置 Owner Config - 仅限 Factory Owner 使用,设置默认硬顶等全局参数">{t('factory.ownerConfig')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showOwnerConfig ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
@@ -518,7 +827,7 @@ export function FactoryPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowBatchOps(!showBatchOps)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('factory.batchOperations')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="批量操作 Batch Operations - 同时对多个金库执行更新价格、设置硬顶、设置赎回时间等操作">{t('factory.batchOperations')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showBatchOps ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
@@ -617,7 +926,7 @@ export function FactoryPanel() {
|
||||
</div>
|
||||
|
||||
{/* 批量设置赎回时间 */}
|
||||
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.batchSetRedemptionTime')}</h5>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<input
|
||||
@@ -636,6 +945,32 @@ export function FactoryPanel() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 批量暂停/恢复金库 */}
|
||||
<div style={{ padding: '10px', background: '#fff3e0', borderRadius: '6px' }}>
|
||||
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px', color: '#e65100' }}>{t('factory.batchPauseUnpause')}</h5>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button
|
||||
onClick={handleBatchPauseVaults}
|
||||
disabled={isProcessing || selectedVaultsForBatch.length === 0}
|
||||
className="btn btn-sm"
|
||||
style={{ flex: 1, background: '#ff9800', color: '#fff', border: 'none' }}
|
||||
>
|
||||
{isProcessing ? '...' : t('factory.pauseSelected')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleBatchUnpauseVaults}
|
||||
disabled={isProcessing || selectedVaultsForBatch.length === 0}
|
||||
className="btn btn-sm"
|
||||
style={{ flex: 1, background: '#4caf50', color: '#fff', border: 'none' }}
|
||||
>
|
||||
{isProcessing ? '...' : t('factory.unpauseSelected')}
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ fontSize: '11px', color: '#e65100', marginTop: '6px' }}>
|
||||
{t('factory.pauseWarning')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -646,7 +981,7 @@ export function FactoryPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('factory.advancedFunctions')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="高级功能 Advanced Functions - 设置金库实现地址、升级金库合约等高级操作(危险)">{t('factory.advancedFunctions')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showAdvanced ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
@@ -657,6 +992,37 @@ export function FactoryPanel() {
|
||||
{t('factory.advancedWarning')}
|
||||
</div>
|
||||
|
||||
{/* 验证金库地址 */}
|
||||
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }} title="验证地址是否为有效金库 / Verify if address is a valid vault">验证金库地址</h5>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={checkVaultAddr}
|
||||
onChange={(e) => setCheckVaultAddr(e.target.value)}
|
||||
placeholder="0x... 输入地址"
|
||||
className="input"
|
||||
style={{ flex: 1, fontSize: '12px' }}
|
||||
/>
|
||||
{checkVaultAddr && checkVaultAddr.length === 42 && (
|
||||
<span style={{
|
||||
padding: '4px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
background: '#fff',
|
||||
border: '1px solid #e0e0e0',
|
||||
color: '#333'
|
||||
}}>
|
||||
{isValidVault ? '[OK] 有效金库' : '[NO] 不是金库'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ fontSize: '10px', color: '#666', marginTop: '4px' }}>
|
||||
使用 isVault() 函数验证地址是否是通过工厂创建的有效金库合约
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 设置金库实现 */}
|
||||
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.setVaultImplementation')}</h5>
|
||||
@@ -751,7 +1117,7 @@ export function FactoryPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowPermissionTest(!showPermissionTest)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('test.permissionTests')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="权限测试 Permission Tests - 测试工厂合约的权限控制,验证非 Owner 无法执行管理操作">{t('test.permissionTests')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showPermissionTest ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { TransactionRecord } from '../hooks/useTransactionHistory'
|
||||
|
||||
@@ -8,6 +9,17 @@ interface Props {
|
||||
|
||||
export function TransactionHistory({ transactions, onClear }: Props) {
|
||||
const { t } = useTranslation()
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||
|
||||
const handleCopy = async (hash: string, txId: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(hash)
|
||||
setCopiedId(txId)
|
||||
setTimeout(() => setCopiedId(null), 1500)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
const labels: Record<string, string> = {
|
||||
@@ -75,13 +87,31 @@ export function TransactionHistory({ transactions, onClear }: Props) {
|
||||
</div>
|
||||
<div className="tx-hash">
|
||||
{tx.hash && typeof tx.hash === 'string' && tx.hash.startsWith('0x') ? (
|
||||
<a
|
||||
href={`https://sepolia.arbiscan.io/tx/${tx.hash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{shortenHash(tx.hash)}
|
||||
</a>
|
||||
<>
|
||||
<a
|
||||
href={`https://sepolia.arbiscan.io/tx/${tx.hash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{shortenHash(tx.hash)}
|
||||
</a>
|
||||
<button
|
||||
onClick={() => handleCopy(tx.hash, tx.id)}
|
||||
className="btn-copy"
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '2px 4px',
|
||||
marginLeft: '4px',
|
||||
fontSize: '11px',
|
||||
color: copiedId === tx.id ? '#4caf50' : '#666',
|
||||
}}
|
||||
title={t('toast.copySuccess')}
|
||||
>
|
||||
{copiedId === tx.id ? 'OK' : 'Copy'}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-muted">-</span>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useState, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAccount, useReadContract, useReadContracts, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
|
||||
import { parseUnits, formatUnits, maxUint256 } from 'viem'
|
||||
import { CONTRACTS, GAS_CONFIG, VAULT_ABI, WUSD_ABI, FACTORY_ABI } from '../config/contracts'
|
||||
import { CONTRACTS, GAS_CONFIG, VAULT_ABI, WUSD_ABI, FACTORY_ABI, YT_REWARD_ROUTER_ABI, YT_VAULT_ABI } from '../config/contracts'
|
||||
import { useMemo } from 'react'
|
||||
import { useTransactions } from '../context/TransactionContext'
|
||||
import type { TransactionType } from '../context/TransactionContext'
|
||||
import { useToast } from './Toast'
|
||||
@@ -24,7 +25,7 @@ export function VaultPanel() {
|
||||
const [selectedVault, setSelectedVault] = useState<{ name: string; address: string }>(DEFAULT_VAULTS[0])
|
||||
const [buyAmount, setBuyAmount] = useState('')
|
||||
const [sellAmount, setSellAmount] = useState('')
|
||||
const [activeTab, setActiveTab] = useState<'buy' | 'sell'>('buy')
|
||||
const [activeTab, setActiveTab] = useState<'buy' | 'sell' | 'transfer'>('buy')
|
||||
const [showBoundaryTest, setShowBoundaryTest] = useState(false)
|
||||
const [testResult, setTestResult] = useState<{ type: 'success' | 'error' | 'pending', msg: string } | null>(null)
|
||||
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
||||
@@ -42,6 +43,21 @@ export function VaultPanel() {
|
||||
const [withdrawManagedAmount, setWithdrawManagedAmount] = useState('')
|
||||
const [withdrawToAddress, setWithdrawToAddress] = useState('')
|
||||
|
||||
// YT 转账状态
|
||||
const [transferYtTo, setTransferYtTo] = useState('')
|
||||
const [transferYtAmount, setTransferYtAmount] = useState('')
|
||||
|
||||
// 倒计时状态
|
||||
const [countdown, setCountdown] = useState<number>(0)
|
||||
|
||||
// 排队退出 - 批量处理
|
||||
const [batchSize, setBatchSize] = useState('10')
|
||||
|
||||
// 硬顶 Swap 状态
|
||||
const [showHardcapSwap, setShowHardcapSwap] = useState(false)
|
||||
const [hardcapSwapAmount, setHardcapSwapAmount] = useState('')
|
||||
const [hardcapSwapSlippage, setHardcapSwapSlippage] = useState('0.5')
|
||||
|
||||
// 从合约动态读取所有金库地址
|
||||
const { data: allVaultsData } = useReadContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
@@ -152,13 +168,6 @@ 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,
|
||||
@@ -173,6 +182,133 @@ export function VaultPanel() {
|
||||
functionName: 'owner',
|
||||
})
|
||||
|
||||
// 读取金库的 factory 地址
|
||||
const { data: vaultFactory } = useReadContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'factory',
|
||||
})
|
||||
|
||||
// 读取金库的 wusdAddress
|
||||
const { data: vaultWusdAddress } = useReadContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'wusdAddress',
|
||||
})
|
||||
|
||||
// 读取 PRICE_PRECISION 常量
|
||||
const { data: pricePrecision } = useReadContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'PRICE_PRECISION',
|
||||
})
|
||||
|
||||
// ===== 硬顶 Swap 相关数据读取 =====
|
||||
// WUSD 对 YT_REWARD_ROUTER 的授权额度
|
||||
const { data: wusdRouterAllowance, refetch: refetchWusdRouterAllowance } = useReadContract({
|
||||
address: CONTRACTS.WUSD,
|
||||
abi: WUSD_ABI,
|
||||
functionName: 'allowance',
|
||||
args: address ? [address, CONTRACTS.YT_REWARD_ROUTER] : undefined,
|
||||
})
|
||||
|
||||
// 读取 LP 池的 swap 开关状态
|
||||
const { data: lpSwapEnabled } = useReadContract({
|
||||
address: CONTRACTS.YT_VAULT,
|
||||
abi: YT_VAULT_ABI,
|
||||
functionName: 'isSwapEnabled',
|
||||
})
|
||||
|
||||
// 读取 WUSD 在 LP 池中的白名单状态
|
||||
const { data: wusdWhitelisted } = useReadContract({
|
||||
address: CONTRACTS.YT_VAULT,
|
||||
abi: YT_VAULT_ABI,
|
||||
functionName: 'whitelistedTokens',
|
||||
args: [CONTRACTS.WUSD],
|
||||
})
|
||||
|
||||
// 读取目标 YT 在 LP 池中的白名单状态
|
||||
const { data: targetYtWhitelisted } = useReadContract({
|
||||
address: CONTRACTS.YT_VAULT,
|
||||
abi: YT_VAULT_ABI,
|
||||
functionName: 'whitelistedTokens',
|
||||
args: [selectedVault.address as `0x${string}`],
|
||||
})
|
||||
|
||||
// 读取 WUSD 的价格 (用于 swap 预览)
|
||||
const { data: wusdMinPrice } = useReadContract({
|
||||
address: CONTRACTS.YT_VAULT,
|
||||
abi: YT_VAULT_ABI,
|
||||
functionName: 'getMinPrice',
|
||||
args: [CONTRACTS.WUSD],
|
||||
})
|
||||
|
||||
// 读取目标 YT 的价格 (用于 swap 预览)
|
||||
const { data: targetYtMaxPrice } = useReadContract({
|
||||
address: CONTRACTS.YT_VAULT,
|
||||
abi: YT_VAULT_ABI,
|
||||
functionName: 'getMaxPrice',
|
||||
args: [selectedVault.address as `0x${string}`],
|
||||
})
|
||||
|
||||
// 读取目标 YT 在 LP 池中的 USDY 余额 (流动性)
|
||||
const { data: targetYtUsdyAmount } = useReadContract({
|
||||
address: CONTRACTS.YT_VAULT,
|
||||
abi: YT_VAULT_ABI,
|
||||
functionName: 'usdyAmounts',
|
||||
args: [selectedVault.address as `0x${string}`],
|
||||
})
|
||||
|
||||
// ===== 排队退出相关数据读取 =====
|
||||
// 读取队列进度
|
||||
const { data: queueProgress, refetch: refetchQueueProgress } = useReadContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'getQueueProgress',
|
||||
})
|
||||
|
||||
// 读取用户待处理请求
|
||||
const { data: userPendingRequests, refetch: refetchUserPendingRequests } = useReadContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'getUserPendingRequests',
|
||||
args: address ? [address] : undefined,
|
||||
})
|
||||
|
||||
|
||||
// 计算是否达到硬顶 (使用 totalSupply 判断)
|
||||
const isAtHardCap = useMemo(() => {
|
||||
if (!vaultInfo) return false
|
||||
const totalSupply = vaultInfo[3] as bigint
|
||||
const hardCap = vaultInfo[4] as bigint
|
||||
return hardCap > 0n && totalSupply >= hardCap
|
||||
}, [vaultInfo])
|
||||
|
||||
// 计算硬顶 swap 预览金额
|
||||
const hardcapSwapPreview = useMemo(() => {
|
||||
if (!hardcapSwapAmount || !wusdMinPrice || !targetYtMaxPrice) return null
|
||||
if (targetYtMaxPrice === 0n) return null
|
||||
try {
|
||||
const amountIn = parseUnits(hardcapSwapAmount, 18)
|
||||
// WUSD 价格和 YT 价格都是 30 位精度
|
||||
const amountOut = (amountIn * wusdMinPrice) / targetYtMaxPrice
|
||||
return amountOut
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [hardcapSwapAmount, wusdMinPrice, targetYtMaxPrice])
|
||||
|
||||
// 检查硬顶 swap 是否需要授权
|
||||
const needsHardcapSwapApproval = useMemo(() => {
|
||||
if (!wusdRouterAllowance || !hardcapSwapAmount) return false
|
||||
try {
|
||||
const amountIn = parseUnits(hardcapSwapAmount, 18)
|
||||
return wusdRouterAllowance < amountIn
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}, [wusdRouterAllowance, hardcapSwapAmount])
|
||||
|
||||
const { writeContract, data: hash, isPending, reset, error: writeError, status: writeStatus } = useWriteContract()
|
||||
|
||||
const { isLoading: isConfirming, isSuccess, isError, error: txError } = useWaitForTransactionReceipt({
|
||||
@@ -220,12 +356,17 @@ export function VaultPanel() {
|
||||
refetchYtBalance()
|
||||
refetchWusdBalance()
|
||||
refetchAllowance()
|
||||
refetchManagedAssets()
|
||||
refetchManagerAllowance()
|
||||
refetchWusdRouterAllowance()
|
||||
refetchQueueProgress()
|
||||
refetchUserPendingRequests()
|
||||
setBuyAmount('')
|
||||
setSellAmount('')
|
||||
setDepositManagedAmount('')
|
||||
setWithdrawManagedAmount('')
|
||||
setTransferYtTo('')
|
||||
setTransferYtAmount('')
|
||||
setHardcapSwapAmount('')
|
||||
reset()
|
||||
}
|
||||
}, [isSuccess])
|
||||
@@ -337,6 +478,49 @@ export function VaultPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
// 授权 WUSD 给 YT_REWARD_ROUTER (用于硬顶 swap)
|
||||
const handleApproveWusdForRouter = async () => {
|
||||
if (!address) return
|
||||
recordTx('approve', undefined, 'WUSD->Router')
|
||||
writeContract({
|
||||
address: CONTRACTS.WUSD,
|
||||
abi: WUSD_ABI,
|
||||
functionName: 'approve',
|
||||
args: [CONTRACTS.YT_REWARD_ROUTER, maxUint256],
|
||||
gas: GAS_CONFIG.SIMPLE,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 硬顶 Swap: 用 WUSD 换取目标 YT
|
||||
const handleHardcapSwap = async () => {
|
||||
if (!hardcapSwapAmount || !address) return
|
||||
const amountIn = parseUnits(hardcapSwapAmount, 18)
|
||||
// 计算最小输出 (考虑滑点)
|
||||
const slippageBps = Math.floor(parseFloat(hardcapSwapSlippage) * 100) // 0.5% -> 50
|
||||
let minOut = 0n
|
||||
if (hardcapSwapPreview) {
|
||||
minOut = hardcapSwapPreview - (hardcapSwapPreview * BigInt(slippageBps) / 10000n)
|
||||
}
|
||||
recordTx('swap', hardcapSwapAmount, `WUSD -> ${selectedVault.name}`)
|
||||
writeContract({
|
||||
address: CONTRACTS.YT_REWARD_ROUTER,
|
||||
abi: YT_REWARD_ROUTER_ABI,
|
||||
functionName: 'swapYT',
|
||||
args: [
|
||||
CONTRACTS.WUSD, // tokenIn
|
||||
selectedVault.address as `0x${string}`, // tokenOut (目标 YT)
|
||||
amountIn, // amountIn
|
||||
minOut, // minOut
|
||||
address // receiver
|
||||
],
|
||||
gas: GAS_CONFIG.COMPLEX,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
const handleBuy = async () => {
|
||||
if (!buyAmount) return
|
||||
recordTx('buy', buyAmount, 'WUSD')
|
||||
@@ -367,11 +551,12 @@ export function VaultPanel() {
|
||||
|
||||
// ===== 管理员功能 =====
|
||||
|
||||
// 更新金库价格 (Factory.updateVaultPrices)
|
||||
// 更新金库价格 (Factory.updateVaultPrices) - 同时更新两个
|
||||
// wusdPrice: 18位精度, ytPrice: 30位精度
|
||||
const handleUpdatePrices = () => {
|
||||
if (!address) return
|
||||
const wusdPrice = parseUnits(priceUpdateForm.wusdPrice, 18)
|
||||
const ytPrice = parseUnits(priceUpdateForm.ytPrice, 18)
|
||||
const ytPrice = parseUnits(priceUpdateForm.ytPrice, 30)
|
||||
recordTx('test', `${priceUpdateForm.wusdPrice}/${priceUpdateForm.ytPrice}`, 'UpdatePrices')
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
@@ -384,6 +569,40 @@ export function VaultPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
// 单独更新 wusdPrice (保持 ytPrice 不变)
|
||||
const handleUpdateWusdPriceOnly = () => {
|
||||
if (!address || !vaultInfo) return
|
||||
const newWusdPrice = parseUnits(priceUpdateForm.wusdPrice, 18)
|
||||
const currentYtPrice = (vaultInfo as any)[6] as bigint // 保持当前 ytPrice (30位精度)
|
||||
recordTx('test', priceUpdateForm.wusdPrice, 'UpdateWusdPrice')
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'updateVaultPrices',
|
||||
args: [selectedVault.address as `0x${string}`, newWusdPrice, currentYtPrice],
|
||||
gas: GAS_CONFIG.STANDARD,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 单独更新 ytPrice (保持 wusdPrice 不变)
|
||||
const handleUpdateYtPriceOnly = () => {
|
||||
if (!address || !vaultInfo) return
|
||||
const currentWusdPrice = (vaultInfo as any)[5] as bigint // 保持当前 wusdPrice (18位精度)
|
||||
const newYtPrice = parseUnits(priceUpdateForm.ytPrice, 30) // ytPrice 是30位精度
|
||||
recordTx('test', priceUpdateForm.ytPrice, 'UpdateYtPrice')
|
||||
writeContract({
|
||||
address: CONTRACTS.FACTORY,
|
||||
abi: FACTORY_ABI,
|
||||
functionName: 'updateVaultPrices',
|
||||
args: [selectedVault.address as `0x${string}`, currentWusdPrice, newYtPrice],
|
||||
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
|
||||
@@ -431,6 +650,25 @@ export function VaultPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
// ===== 排队退出 - 批量处理功能 =====
|
||||
|
||||
// 批量处理退出请求 (Manager/Factory Owner 调用)
|
||||
const handleProcessBatchWithdrawals = () => {
|
||||
if (!address || !batchSize) return
|
||||
const size = parseInt(batchSize, 10)
|
||||
if (isNaN(size) || size <= 0) return
|
||||
recordTx('test', batchSize, 'ProcessBatch')
|
||||
writeContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'processBatchWithdrawals',
|
||||
args: [BigInt(size)],
|
||||
gas: GAS_CONFIG.COMPLEX,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Manager 资产管理功能 =====
|
||||
|
||||
// Manager 授权 WUSD 给 Vault (用于 depositManagedAssets)
|
||||
@@ -484,6 +722,26 @@ export function VaultPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
// YT 代币转账
|
||||
const handleTransferYt = () => {
|
||||
if (!address || !transferYtTo || !transferYtAmount) return
|
||||
// 验证地址格式
|
||||
if (!transferYtTo.startsWith('0x') || transferYtTo.length !== 42) {
|
||||
showToast('error', t('vault.invalidAddress'))
|
||||
return
|
||||
}
|
||||
recordTx('transfer', transferYtAmount, vaultSymbol || 'YT')
|
||||
writeContract({
|
||||
address: selectedVault.address as `0x${string}`,
|
||||
abi: VAULT_ABI,
|
||||
functionName: 'transfer',
|
||||
args: [transferYtTo as `0x${string}`, parseUnits(transferYtAmount, 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<string | null>(null)
|
||||
@@ -630,6 +888,22 @@ export function VaultPanel() {
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
// 同步合约值到倒计时状态,并启动定时器
|
||||
useEffect(() => {
|
||||
if (timeUntilRedeem !== undefined) {
|
||||
setCountdown(Number(timeUntilRedeem))
|
||||
}
|
||||
}, [timeUntilRedeem])
|
||||
|
||||
// 倒计时定时器
|
||||
useEffect(() => {
|
||||
if (countdown <= 0) return
|
||||
const timer = setInterval(() => {
|
||||
setCountdown(prev => Math.max(0, prev - 1))
|
||||
}, 1000)
|
||||
return () => clearInterval(timer)
|
||||
}, [countdown > 0])
|
||||
|
||||
// 计算按钮是否应该禁用 - 排除错误状态
|
||||
const isProcessing = (isPending || isConfirming) && writeStatus !== 'error'
|
||||
|
||||
@@ -668,48 +942,68 @@ export function VaultPanel() {
|
||||
<h3>{vaultName || selectedVault.name} ({vaultSymbol || '-'})</h3>
|
||||
<div className="info-grid">
|
||||
<div className="info-item">
|
||||
<span>{t('vault.totalAssets')}</span>
|
||||
<span title="总资产 Total Assets - 金库中存入的 WUSD 总量,包括闲置资产和托管资产" style={{ cursor: 'help' }}>{t('vault.totalAssets')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[0], 18) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>{t('vault.idleAssets')}</span>
|
||||
<span title="闲置资产 Idle Assets - 金库中未被 Manager 提取管理的资产,可用于用户赎回" style={{ cursor: 'help' }}>{t('vault.idleAssets')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[1], 18) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>{t('vault.totalSupply')}</span>
|
||||
<span title="总供应 Total Supply - 已发行的 YT 代币总量,代表用户的份额凭证" style={{ cursor: 'help' }}>{t('vault.totalSupply')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[3], 18) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>{t('vault.hardCap')}</span>
|
||||
<span title="硬顶 Hard Cap - 金库可接受的最大存款额度,达到后无法继续存入" style={{ cursor: 'help' }}>{t('vault.hardCap')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[4], 18) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>{t('vault.wusdPrice')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[5], 18) : '0'}</strong>
|
||||
<span title="WUSD价格 WUSD Price - 稳定币 WUSD 的计价,通常为 1.0,30位精度" style={{ cursor: 'help' }}>{t('vault.wusdPrice')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[5], 30) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span>{t('vault.ytPrice')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[6], 18) : '0'}</strong>
|
||||
<span title="YT价格 YT Price - YT 代币的价格,30位精度,用于计算买入/卖出的兑换比例" style={{ cursor: 'help' }}>{t('vault.ytPrice')}</span>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[6], 30) : '0'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 合约信息 */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '12px', fontSize: '12px' }}>
|
||||
<div style={{ padding: '8px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<div style={{ color: '#666', marginBottom: '2px', cursor: 'help' }} title="金库地址 Vault Address - 当前选择的 YT 资产金库合约地址">{t('vault.vaultAddress')}</div>
|
||||
<code style={{ fontSize: '10px', wordBreak: 'break-all' }}>{selectedVault.address}</code>
|
||||
</div>
|
||||
<div style={{ padding: '8px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<div style={{ color: '#666', marginBottom: '2px', cursor: 'help' }} title="工厂地址 Factory Address - 创建和管理所有金库的工厂合约地址">{t('vault.factoryAddress')}</div>
|
||||
<code style={{ fontSize: '10px', wordBreak: 'break-all' }}>{vaultFactory || '-'}</code>
|
||||
</div>
|
||||
<div style={{ padding: '8px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<div style={{ color: '#666', marginBottom: '2px', cursor: 'help' }} title="WUSD合约 WUSD Contract - 稳定币 WUSD 的 ERC20 合约地址">{t('vault.wusdContract')}</div>
|
||||
<code style={{ fontSize: '10px', wordBreak: 'break-all' }}>{vaultWusdAddress || '-'}</code>
|
||||
</div>
|
||||
<div style={{ padding: '8px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
<div style={{ color: '#666', marginBottom: '2px', cursor: 'help' }} title="价格精度 Price Precision - 价格计算使用的精度值,通常为 1e30">{t('vault.pricePrecision')}</div>
|
||||
<strong>{pricePrecision ? pricePrecision.toString() : '-'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 角色信息 */}
|
||||
<div className="role-info">
|
||||
<div className="info-row">
|
||||
<span>Factory Owner:</span>
|
||||
<span title="工厂所有者 Factory Owner - 拥有最高管理权限,可设置金库参数、更新价格、设置管理员等" style={{ cursor: 'help' }}>Factory Owner:</span>
|
||||
<code className={address && factoryOwner && address.toLowerCase() === factoryOwner.toLowerCase() ? 'highlight' : ''}>
|
||||
{factoryOwner || '-'}
|
||||
</code>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>Vault Manager:</span>
|
||||
<span title="金库管理员 Vault Manager - 可管理金库资产,执行 depositManagedAssets 和 withdrawForManagement 操作" style={{ cursor: 'help' }}>Vault Manager:</span>
|
||||
<code className={address && vaultManager && address.toLowerCase() === vaultManager.toLowerCase() ? 'highlight' : ''}>
|
||||
{vaultManager || '-'}
|
||||
</code>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('test.role')}:</span>
|
||||
<span title="当前角色 Current Role - 你当前连接钱包在此金库中的角色权限" style={{ cursor: 'help' }}>{t('test.role')}:</span>
|
||||
<strong className={
|
||||
address && factoryOwner && address.toLowerCase() === factoryOwner.toLowerCase() ? 'text-success' :
|
||||
address && vaultManager && address.toLowerCase() === vaultManager.toLowerCase() ? 'text-warning' : ''
|
||||
@@ -722,11 +1016,11 @@ export function VaultPanel() {
|
||||
|
||||
<div className="balance-info">
|
||||
<div className="info-row">
|
||||
<span>{t('vault.yourWusdBalance')}:</span>
|
||||
<span title="你的 WUSD 余额 Your WUSD Balance - 你钱包中持有的稳定币 WUSD 数量,可用于购买 YT" style={{ cursor: 'help' }}>{t('vault.yourWusdBalance')}:</span>
|
||||
<strong>{wusdBalance ? formatUnits(wusdBalance, 18) : '0'}</strong>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('vault.yourYtBalance')} ({selectedVault.name}):</span>
|
||||
<span title="你的 YT 余额 Your YT Balance - 你持有的当前金库 YT 代币数量,可在赎回期卖出换回 WUSD" style={{ cursor: 'help' }}>{t('vault.yourYtBalance')} ({selectedVault.name}):</span>
|
||||
<strong>{ytBalance ? formatUnits(ytBalance, 18) : '0'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@@ -744,6 +1038,12 @@ export function VaultPanel() {
|
||||
>
|
||||
{t('vault.sellYt')}
|
||||
</button>
|
||||
<button
|
||||
className={`tab ${activeTab === 'transfer' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('transfer')}
|
||||
>
|
||||
{t('vault.transferYt')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{activeTab === 'buy' && (
|
||||
@@ -759,14 +1059,28 @@ export function VaultPanel() {
|
||||
/>
|
||||
</div>
|
||||
{previewBuyAmount && buyAmount && (
|
||||
<div className="preview">
|
||||
{t('vault.youWillReceive')}: <strong>{formatUnits(previewBuyAmount, 18)} YT</strong>
|
||||
<div style={{ marginBottom: '12px', padding: '10px', background: '#e8f5e9', borderRadius: '6px', fontSize: '13px' }}>
|
||||
<span style={{ color: '#666' }}>{t('vault.youWillReceive')}: </span>
|
||||
<strong style={{ color: '#2e7d32', fontSize: '15px' }}>{formatUnits(previewBuyAmount, 18)} YT</strong>
|
||||
</div>
|
||||
)}
|
||||
{/* Debug: 显示授权状态 */}
|
||||
<div className="debug-info" style={{ fontSize: '12px', color: '#999', marginBottom: '8px' }}>
|
||||
授权额度: {wusdAllowance !== undefined ? formatUnits(wusdAllowance, 18) : 'loading...'} WUSD
|
||||
{buyAmount && ` | 需要: ${buyAmount} | 需授权: ${needsApproval ? '是' : '否'}`}
|
||||
{/* Debug: 显示预估计算详情 */}
|
||||
<div className="debug-info" style={{ fontSize: '11px', color: '#999', marginBottom: '8px', background: '#f5f5f5', padding: '8px', borderRadius: '4px' }}>
|
||||
<div>授权额度: {wusdAllowance !== undefined ? formatUnits(wusdAllowance, 18) : 'loading...'} WUSD
|
||||
{buyAmount && ` | 需要: ${buyAmount} | 需授权: ${needsApproval ? '是' : '否'}`}
|
||||
</div>
|
||||
{previewBuyAmount && (
|
||||
<div style={{ marginTop: '4px', fontFamily: 'monospace' }}>
|
||||
previewBuy 原始值: {previewBuyAmount.toString()}
|
||||
{vaultInfo && (
|
||||
<>
|
||||
<br />ytPrice: {(vaultInfo[6] as bigint).toString()} (显示: {formatUnits(vaultInfo[6] as bigint, 30)})
|
||||
<br />wusdPrice: {(vaultInfo[5] as bigint).toString()} (显示: {formatUnits(vaultInfo[5] as bigint, 30)})
|
||||
{pricePrecision && <><br />PRICE_PRECISION: {pricePrecision.toString()}</>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{needsApproval ? (
|
||||
<button
|
||||
@@ -788,15 +1102,135 @@ export function VaultPanel() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 硬顶 Swap YT 交易区域 */}
|
||||
{activeTab === 'buy' && (
|
||||
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowHardcapSwap(!showHardcapSwap)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>
|
||||
{isAtHardCap ? '⚠️ 金库已达硬顶 - 使用 LP 池 Swap 获取 YT' : '硬顶 Swap YT 交易'}
|
||||
</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showHardcapSwap ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
{(showHardcapSwap || isAtHardCap) && (
|
||||
<div style={{ marginTop: '10px', padding: '12px', background: '#fff', borderRadius: '6px' }}>
|
||||
{isAtHardCap && (
|
||||
<div style={{ marginBottom: '12px', padding: '8px', background: '#f8f9fa', borderRadius: '4px', fontSize: '12px', color: '#666' }}>
|
||||
当前金库已达到硬顶限制,无法直接购买。可通过 LP 池用 WUSD Swap 获得 {selectedVault.name}。
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 状态检查 */}
|
||||
<div style={{ marginBottom: '12px', padding: '8px', background: '#fafafa', borderRadius: '6px', fontSize: '11px' }}>
|
||||
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||
<span>LP Swap: <strong style={{ color: lpSwapEnabled ? '#4caf50' : '#f44336' }}>{lpSwapEnabled ? '开启' : '关闭'}</strong></span>
|
||||
<span>WUSD 白名单: <strong style={{ color: wusdWhitelisted ? '#4caf50' : '#f44336' }}>{wusdWhitelisted ? '✓' : '✗'}</strong></span>
|
||||
<span>{selectedVault.name} 白名单: <strong style={{ color: targetYtWhitelisted ? '#4caf50' : '#f44336' }}>{targetYtWhitelisted ? '✓' : '✗'}</strong></span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', marginTop: '4px' }}>
|
||||
<span>WUSD 余额: <strong>{wusdBalance ? formatUnits(wusdBalance, 18) : '0'}</strong></span>
|
||||
<span>Router 授权: <strong style={{ color: !needsHardcapSwapApproval ? '#4caf50' : '#f44336' }}>{wusdRouterAllowance !== undefined ? formatUnits(wusdRouterAllowance, 18) : '...'}</strong></span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', marginTop: '4px' }}>
|
||||
<span>{selectedVault.name} LP流动性: <strong>{targetYtUsdyAmount !== undefined ? formatUnits(targetYtUsdyAmount, 18) : '...'}</strong> USDY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ marginBottom: '8px' }}>
|
||||
<label style={{ fontSize: '12px', color: '#666' }}>WUSD 数量</label>
|
||||
<input
|
||||
type="number"
|
||||
value={hardcapSwapAmount}
|
||||
onChange={(e) => setHardcapSwapAmount(e.target.value)}
|
||||
placeholder="输入 WUSD 数量"
|
||||
className="input"
|
||||
style={{ fontSize: '13px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group" style={{ marginBottom: '8px' }}>
|
||||
<label style={{ fontSize: '12px', color: '#666' }}>滑点容忍度 (%)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={hardcapSwapSlippage}
|
||||
onChange={(e) => setHardcapSwapSlippage(e.target.value)}
|
||||
placeholder="0.5"
|
||||
className="input"
|
||||
style={{ fontSize: '13px', width: '80px' }}
|
||||
step="0.1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 预览到账金额 */}
|
||||
{hardcapSwapAmount && hardcapSwapPreview && (
|
||||
<div style={{ marginBottom: '12px', padding: '10px', background: '#f8f9fa', borderRadius: '6px', fontSize: '13px' }}>
|
||||
<span style={{ color: '#666' }}>{t('vault.youWillReceive')}: </span>
|
||||
<strong style={{ color: '#333', fontSize: '15px' }}>
|
||||
{formatUnits(hardcapSwapPreview, 18)} {selectedVault.name}
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 按钮 */}
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
{needsHardcapSwapApproval ? (
|
||||
<button
|
||||
onClick={handleApproveWusdForRouter}
|
||||
disabled={isProcessing}
|
||||
className="btn btn-secondary btn-sm"
|
||||
>
|
||||
{isProcessing ? t('common.processing') : '授权 WUSD'}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleHardcapSwap}
|
||||
disabled={isProcessing || !hardcapSwapAmount || !lpSwapEnabled || !wusdWhitelisted || !targetYtWhitelisted}
|
||||
className="btn btn-primary btn-sm"
|
||||
>
|
||||
{isProcessing ? t('common.processing') : `Swap 获取 ${selectedVault.name}`}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'sell' && (
|
||||
<div className="trade-section">
|
||||
{/* 排队退出说明 */}
|
||||
<div style={{ marginBottom: '12px', padding: '10px', background: '#f8f9fa', borderRadius: '6px', fontSize: '12px', color: '#666' }}>
|
||||
{t('vault.queueWithdrawDesc')}
|
||||
</div>
|
||||
|
||||
{/* 队列进度信息 */}
|
||||
<div style={{ marginBottom: '12px', padding: '10px', background: '#fff', border: '1px solid #e0e0e0', borderRadius: '6px' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', fontSize: '12px' }}>
|
||||
<div>
|
||||
<span style={{ color: '#666' }}>{t('vault.queueTotal')}: </span>
|
||||
<strong>{queueProgress ? (queueProgress as any)[1]?.toString() : '0'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: '#666' }}>{t('vault.queuePending')}: </span>
|
||||
<strong>{queueProgress ? (queueProgress as any)[2]?.toString() : '0'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: '#666' }}>{t('vault.queueProcessed')}: </span>
|
||||
<strong>{queueProgress ? (queueProgress as any)[0]?.toString() : '0'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 赎回状态提示 */}
|
||||
<div className={`redeem-status-banner ${canRedeem ? 'success' : 'warning'}`}>
|
||||
<span>{t('vault.redeemStatus')}: </span>
|
||||
<strong>{canRedeem ? t('vault.redeemAvailable') : t('vault.redeemNotAvailable')}</strong>
|
||||
{!canRedeem && timeUntilRedeem && (
|
||||
{!canRedeem && countdown > 0 && (
|
||||
<span className="time-remaining">
|
||||
({t('vault.timeRemaining')}: {formatTimeRemaining(Number(timeUntilRedeem))})
|
||||
({t('vault.timeRemaining')}: {formatTimeRemaining(countdown)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -813,8 +1247,15 @@ export function VaultPanel() {
|
||||
/>
|
||||
</div>
|
||||
{previewSellAmount && sellAmount && (
|
||||
<div className="preview">
|
||||
{t('vault.youWillReceive')}: <strong>{formatUnits(previewSellAmount, 18)} WUSD</strong>
|
||||
<div style={{ marginBottom: '12px', padding: '10px', background: '#f8f9fa', borderRadius: '6px', fontSize: '13px' }}>
|
||||
<span style={{ color: '#666' }}>{t('vault.youWillReceive')}: </span>
|
||||
<strong style={{ color: '#333', fontSize: '15px' }}>{formatUnits(previewSellAmount, 18)} WUSD</strong>
|
||||
</div>
|
||||
)}
|
||||
{/* Debug: 显示预估计算详情 */}
|
||||
{previewSellAmount && (
|
||||
<div className="debug-info" style={{ fontSize: '11px', color: '#999', marginBottom: '8px', background: '#f5f5f5', padding: '8px', borderRadius: '4px', fontFamily: 'monospace' }}>
|
||||
previewSell: {previewSellAmount.toString()}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
@@ -822,7 +1263,81 @@ export function VaultPanel() {
|
||||
disabled={isProcessing || !sellAmount || !canRedeem}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{isProcessing ? t('common.processing') : !canRedeem ? t('vault.redeemLocked') : t('vault.sell')}
|
||||
{isProcessing ? t('common.processing') : !canRedeem ? t('vault.redeemLocked') : t('vault.requestWithdraw')}
|
||||
</button>
|
||||
|
||||
{/* 用户待处理请求列表 */}
|
||||
{userPendingRequests && (userPendingRequests as any[]).length > 0 && (
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<h4 style={{ fontSize: '13px', color: '#666', marginBottom: '8px' }}>{t('vault.yourPendingRequests')}</h4>
|
||||
<div style={{ border: '1px solid #e0e0e0', borderRadius: '6px', overflow: 'hidden' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '11px' }}>
|
||||
<thead>
|
||||
<tr style={{ background: '#f8f9fa' }}>
|
||||
<th style={{ padding: '8px', textAlign: 'left', borderBottom: '1px solid #e0e0e0' }}>YT {t('common.amount')}</th>
|
||||
<th style={{ padding: '8px', textAlign: 'left', borderBottom: '1px solid #e0e0e0' }}>WUSD {t('common.amount')}</th>
|
||||
<th style={{ padding: '8px', textAlign: 'left', borderBottom: '1px solid #e0e0e0' }}>{t('vault.requestTime')}</th>
|
||||
<th style={{ padding: '8px', textAlign: 'center', borderBottom: '1px solid #e0e0e0' }}>{t('vault.status')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(userPendingRequests as any[]).map((req, idx) => (
|
||||
<tr key={idx} style={{ borderBottom: idx < (userPendingRequests as any[]).length - 1 ? '1px solid #f0f0f0' : 'none' }}>
|
||||
<td style={{ padding: '8px' }}>{formatUnits(req.ytAmount, 18)}</td>
|
||||
<td style={{ padding: '8px' }}>{formatUnits(req.wusdAmount, 18)}</td>
|
||||
<td style={{ padding: '8px' }}>{new Date(Number(req.requestTime) * 1000).toLocaleString()}</td>
|
||||
<td style={{ padding: '8px', textAlign: 'center' }}>
|
||||
<span style={{
|
||||
padding: '2px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '10px',
|
||||
background: req.processed ? '#e8f5e9' : '#fff3e0',
|
||||
color: req.processed ? '#2e7d32' : '#e65100'
|
||||
}}>
|
||||
{req.processed ? t('vault.processed') : t('vault.pending')}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'transfer' && (
|
||||
<div className="trade-section">
|
||||
<div className="form-group">
|
||||
<label>{t('vault.toAddress')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={transferYtTo}
|
||||
onChange={(e) => setTransferYtTo(e.target.value)}
|
||||
placeholder="0x..."
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>{t('vault.ytAmount')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={transferYtAmount}
|
||||
onChange={(e) => setTransferYtAmount(e.target.value)}
|
||||
placeholder={t('vault.enterYtAmount')}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '12px' }}>
|
||||
{t('vault.yourYtBalance')}: <strong>{ytBalance ? formatUnits(ytBalance, 18) : '0'}</strong> {selectedVault.name}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleTransferYt}
|
||||
disabled={isProcessing || !transferYtTo || !transferYtAmount}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{isProcessing ? t('common.processing') : t('vault.transfer')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -833,7 +1348,7 @@ export function VaultPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowAdminConfig(!showAdminConfig)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('vault.adminConfig')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="管理员配置 Admin Config - 仅限 Factory Owner 使用,可更新价格、设置硬顶、赎回时间等">{t('vault.adminConfig')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showAdminConfig ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
@@ -848,39 +1363,70 @@ export function VaultPanel() {
|
||||
</div>
|
||||
|
||||
{/* 更新价格 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.updatePrices')}</h4>
|
||||
<div className="form-grid" style={{ marginBottom: '16px' }}>
|
||||
<div className="form-group">
|
||||
<label>{t('vault.wusdPrice')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={priceUpdateForm.wusdPrice}
|
||||
onChange={(e) => setPriceUpdateForm({ ...priceUpdateForm, wusdPrice: e.target.value })}
|
||||
placeholder="1"
|
||||
className="input"
|
||||
step="0.01"
|
||||
/>
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="更新价格 Update Prices - 修改 WUSD 和 YT 的计价,影响买卖兑换比例">{t('vault.updatePrices')}</h4>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginBottom: '16px' }}>
|
||||
{/* WUSD 价格 */}
|
||||
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px', border: '1px solid #dee2e6' }}>
|
||||
<label style={{ fontSize: '12px', color: '#666', display: 'block', marginBottom: '4px' }}>{t('vault.wusdPrice')}</label>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<input
|
||||
type="number"
|
||||
value={priceUpdateForm.wusdPrice}
|
||||
onChange={(e) => setPriceUpdateForm({ ...priceUpdateForm, wusdPrice: e.target.value })}
|
||||
placeholder="1"
|
||||
className="input"
|
||||
style={{ fontSize: '13px', flex: 1 }}
|
||||
step="0.01"
|
||||
/>
|
||||
<button
|
||||
onClick={handleUpdateWusdPriceOnly}
|
||||
disabled={isPending || isConfirming || !vaultInfo}
|
||||
className="btn btn-primary btn-sm"
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{isPending || isConfirming ? '...' : '更新'}
|
||||
</button>
|
||||
</div>
|
||||
<p style={{ fontSize: '10px', color: '#999', margin: '4px 0 0 0' }}>
|
||||
当前: {vaultInfo ? formatUnits((vaultInfo as any)[5], 18) : '-'} (18位精度)
|
||||
</p>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>{t('vault.ytPrice')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={priceUpdateForm.ytPrice}
|
||||
onChange={(e) => setPriceUpdateForm({ ...priceUpdateForm, ytPrice: e.target.value })}
|
||||
placeholder="1"
|
||||
className="input"
|
||||
step="0.01"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||
<button onClick={handleUpdatePrices} disabled={isPending || isConfirming} className="btn btn-primary">
|
||||
{isPending || isConfirming ? '...' : t('vault.updatePrices')}
|
||||
</button>
|
||||
{/* YT 价格 */}
|
||||
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px', border: '1px solid #dee2e6' }}>
|
||||
<label style={{ fontSize: '12px', color: '#666', display: 'block', marginBottom: '4px' }}>{t('vault.ytPrice')}</label>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
<input
|
||||
type="number"
|
||||
value={priceUpdateForm.ytPrice}
|
||||
onChange={(e) => setPriceUpdateForm({ ...priceUpdateForm, ytPrice: e.target.value })}
|
||||
placeholder="1"
|
||||
className="input"
|
||||
style={{ fontSize: '13px', flex: 1 }}
|
||||
step="0.01"
|
||||
/>
|
||||
<button
|
||||
onClick={handleUpdateYtPriceOnly}
|
||||
disabled={isPending || isConfirming || !vaultInfo}
|
||||
className="btn btn-secondary btn-sm"
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{isPending || isConfirming ? '...' : '更新'}
|
||||
</button>
|
||||
</div>
|
||||
<p style={{ fontSize: '10px', color: '#999', margin: '4px 0 0 0' }}>
|
||||
当前: {vaultInfo ? formatUnits((vaultInfo as any)[6], 30) : '-'} (30位精度)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* 同时更新两个价格 */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<button onClick={handleUpdatePrices} disabled={isPending || isConfirming} className="btn btn-outline" style={{ fontSize: '12px' }}>
|
||||
{isPending || isConfirming ? '...' : '同时更新两个价格'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 设置硬顶 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.setHardCap')}</h4>
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="设置硬顶 Set Hard Cap - 设置金库可接受的最大存款额度">{t('vault.setHardCap')}</h4>
|
||||
<div className="form-grid" style={{ marginBottom: '16px' }}>
|
||||
<div className="form-group">
|
||||
<label>{t('vault.newHardCap')}</label>
|
||||
@@ -900,7 +1446,7 @@ export function VaultPanel() {
|
||||
</div>
|
||||
|
||||
{/* 设置赎回时间 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.setRedemptionTime')}</h4>
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="设置赎回时间 Set Redemption Time - 设置下一次允许用户赎回 YT 的时间点">{t('vault.setRedemptionTime')}</h4>
|
||||
<div className="form-grid" style={{ marginBottom: '16px' }}>
|
||||
<div className="form-group">
|
||||
<label>{t('vault.newRedemptionTime')}</label>
|
||||
@@ -919,8 +1465,8 @@ export function VaultPanel() {
|
||||
</div>
|
||||
|
||||
{/* 设置管理员 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.setManager')}</h4>
|
||||
<div className="form-grid">
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="设置管理员 Set Manager - 指定新的金库管理员地址,管理员可管理托管资产">{t('vault.setManager')}</h4>
|
||||
<div className="form-grid" style={{ marginBottom: '16px' }}>
|
||||
<div className="form-group">
|
||||
<label>{t('vault.newManager')}</label>
|
||||
<input
|
||||
@@ -937,6 +1483,52 @@ export function VaultPanel() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 批量处理退出请求 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="批量处理退出请求 - 处理排队中的用户退出请求,发送 WUSD 给用户">{t('vault.processBatchWithdrawals')}</h4>
|
||||
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||
{/* 队列进度信息 */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '12px', fontSize: '12px', marginBottom: '12px' }}>
|
||||
<div>
|
||||
<span style={{ color: '#666' }}>{t('vault.queueTotal')}: </span>
|
||||
<strong>{queueProgress ? (queueProgress as any)[1]?.toString() : '0'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: '#666' }}>{t('vault.queuePending')}: </span>
|
||||
<strong>{queueProgress ? (queueProgress as any)[2]?.toString() : '0'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: '#666' }}>{t('vault.queueProcessed')}: </span>
|
||||
<strong>{queueProgress ? (queueProgress as any)[0]?.toString() : '0'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-grid">
|
||||
<div className="form-group">
|
||||
<label>{t('vault.batchSize')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={batchSize}
|
||||
onChange={(e) => setBatchSize(e.target.value)}
|
||||
placeholder="10"
|
||||
className="input"
|
||||
min="1"
|
||||
max="100"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||
<button
|
||||
onClick={handleProcessBatchWithdrawals}
|
||||
disabled={isPending || isConfirming || !batchSize || (queueProgress && (queueProgress as any)[2]?.toString() === '0')}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{isPending || isConfirming ? '...' : t('vault.processBatch')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontSize: '11px', color: '#999', margin: '8px 0 0 0' }}>
|
||||
{t('vault.processBatchHint')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -947,7 +1539,7 @@ export function VaultPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowManagerPanel(!showManagerPanel)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('vault.managerPanel')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="管理员资产面板 Manager Panel - 仅限 Vault Manager 使用,可存入和提取托管资产">{t('vault.managerPanel')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showManagerPanel ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
@@ -957,7 +1549,7 @@ export function VaultPanel() {
|
||||
<div style={{ marginBottom: '16px', padding: '8px', background: '#f8f9fa', borderRadius: '4px' }}>
|
||||
<div className="info-row">
|
||||
<span>{t('vault.managedAssets')}:</span>
|
||||
<strong>{managedAssets ? formatUnits(managedAssets, 18) : '0'} WUSD</strong>
|
||||
<strong>{vaultInfo ? formatUnits(vaultInfo[2], 18) : '0'} WUSD</strong>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('vault.idleAssets')}:</span>
|
||||
@@ -971,7 +1563,7 @@ export function VaultPanel() {
|
||||
</div>
|
||||
|
||||
{/* 存入托管资产 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.depositManagedAssets')}</h4>
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="存入托管资产 Deposit Managed Assets - Manager 将外部资产存回金库,增加闲置资产">{t('vault.depositManagedAssets')}</h4>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<input
|
||||
type="number"
|
||||
@@ -1010,7 +1602,7 @@ export function VaultPanel() {
|
||||
</div>
|
||||
|
||||
{/* 提取托管资产 */}
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.withdrawForManagement')}</h4>
|
||||
<h4 style={{ fontSize: '14px', marginBottom: '12px', cursor: 'help' }} title="提取托管资产 Withdraw For Management - Manager 从金库提取资产进行外部管理投资">{t('vault.withdrawForManagement')}</h4>
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
<input
|
||||
type="number"
|
||||
@@ -1045,7 +1637,7 @@ export function VaultPanel() {
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowBoundaryTest(!showBoundaryTest)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('test.boundaryTests')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="边界测试 Boundary Tests - 测试合约的边界条件和错误处理,验证合约安全性">{t('test.boundaryTests')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showBoundaryTest ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
@@ -1060,7 +1652,7 @@ export function VaultPanel() {
|
||||
{canRedeem ? t('test.yes') : t('test.no')}
|
||||
</strong>
|
||||
<span style={{ marginLeft: '16px' }}>{t('test.timeToRedeem')}: </span>
|
||||
<strong>{timeUntilRedeem ? `${Number(timeUntilRedeem)}s` : '0s'}</strong>
|
||||
<strong>{countdown > 0 ? formatTimeRemaining(countdown) : '0s'}</strong>
|
||||
</div>
|
||||
|
||||
<div className="test-grid">
|
||||
|
||||
@@ -15,6 +15,8 @@ export function WUSDPanel() {
|
||||
const { showToast } = useToast()
|
||||
const [mintAmount, setMintAmount] = useState('')
|
||||
const [burnAmount, setBurnAmount] = useState('')
|
||||
const [transferTo, setTransferTo] = useState('')
|
||||
const [transferAmount, setTransferAmount] = useState('')
|
||||
const [showBoundaryTest, setShowBoundaryTest] = useState(false)
|
||||
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
||||
|
||||
@@ -37,6 +39,24 @@ export function WUSDPanel() {
|
||||
functionName: 'decimals',
|
||||
})
|
||||
|
||||
const { data: totalSupply, refetch: refetchTotalSupply } = useReadContract({
|
||||
address: CONTRACTS.WUSD,
|
||||
abi: WUSD_ABI,
|
||||
functionName: 'totalSupply',
|
||||
})
|
||||
|
||||
const { data: owner } = useReadContract({
|
||||
address: CONTRACTS.WUSD,
|
||||
abi: WUSD_ABI,
|
||||
functionName: 'owner',
|
||||
})
|
||||
|
||||
const { data: name } = useReadContract({
|
||||
address: CONTRACTS.WUSD,
|
||||
abi: WUSD_ABI,
|
||||
functionName: 'name',
|
||||
})
|
||||
|
||||
const { writeContract, data: hash, isPending, error: writeError, reset, status: writeStatus } = useWriteContract()
|
||||
|
||||
const { isLoading: isConfirming, isSuccess, isError, error: txError } = useWaitForTransactionReceipt({
|
||||
@@ -81,7 +101,11 @@ export function WUSDPanel() {
|
||||
pendingTxRef.current = null
|
||||
}
|
||||
refetchBalance()
|
||||
refetchTotalSupply()
|
||||
setMintAmount('')
|
||||
setBurnAmount('')
|
||||
setTransferTo('')
|
||||
setTransferAmount('')
|
||||
}
|
||||
}, [isSuccess])
|
||||
|
||||
@@ -182,6 +206,25 @@ export function WUSDPanel() {
|
||||
})
|
||||
}
|
||||
|
||||
const handleTransfer = async () => {
|
||||
if (!address || !transferTo || !transferAmount || !decimals) return
|
||||
// 验证地址格式
|
||||
if (!transferTo.startsWith('0x') || transferTo.length !== 42) {
|
||||
showToast('error', t('wusd.invalidAddress'))
|
||||
return
|
||||
}
|
||||
recordTx('transfer', transferAmount, 'WUSD')
|
||||
writeContract({
|
||||
address: CONTRACTS.WUSD,
|
||||
abi: WUSD_ABI,
|
||||
functionName: 'transfer',
|
||||
args: [transferTo as `0x${string}`, parseUnits(transferAmount, decimals)],
|
||||
gas: GAS_CONFIG.STANDARD,
|
||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||
})
|
||||
}
|
||||
|
||||
// 边界测试函数
|
||||
const runBoundaryTest = (testType: string) => {
|
||||
if (!address || !decimals) return
|
||||
@@ -237,24 +280,54 @@ export function WUSDPanel() {
|
||||
return (
|
||||
<div className="panel">
|
||||
<h2>{t('wusd.title')}</h2>
|
||||
<div className="info-row">
|
||||
<span>{t('common.contract')}:</span>
|
||||
<code>{CONTRACTS.WUSD}</code>
|
||||
|
||||
{/* 代币信息卡片 */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '12px', marginBottom: '16px' }}>
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="代币名称 Token Name - WUSD 稳定币的完整名称">名称</div>
|
||||
<strong style={{ fontSize: '14px' }}>{name || 'WUSD'}</strong>
|
||||
</div>
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="代币符号 Token Symbol - WUSD 稳定币的交易符号">符号</div>
|
||||
<strong style={{ fontSize: '14px' }}>{symbol || 'WUSD'}</strong>
|
||||
</div>
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="精度 Decimals - 代币小数位数">精度</div>
|
||||
<strong style={{ fontSize: '14px' }}>{decimals?.toString() || '18'}</strong>
|
||||
</div>
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="合约地址 Contract Address - WUSD 稳定币的 ERC20 合约地址">{t('common.contract')}</div>
|
||||
<code style={{ fontSize: '11px', wordBreak: 'break-all' }}>{CONTRACTS.WUSD}</code>
|
||||
</div>
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="所有者 Owner - WUSD 合约的所有者地址,拥有铸造和销毁权限">{t('wusd.owner')}</div>
|
||||
<code style={{ fontSize: '11px', wordBreak: 'break-all' }}>{owner ? `${(owner as string).slice(0, 10)}...${(owner as string).slice(-8)}` : '-'}</code>
|
||||
</div>
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="总供应量 Total Supply - WUSD 代币的全网发行总量">{t('wusd.totalSupply')}</div>
|
||||
<strong style={{ fontSize: '14px' }}>
|
||||
{totalSupply !== undefined && decimals !== undefined
|
||||
? Number(formatUnits(totalSupply, decimals)).toLocaleString()
|
||||
: '0'}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-row">
|
||||
<span>{t('common.balance')}:</span>
|
||||
<strong>
|
||||
|
||||
{/* 用户余额 */}
|
||||
<div style={{ padding: '12px', background: '#f8f9fa', borderRadius: '8px', marginBottom: '16px' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px', cursor: 'help' }} title="余额 Balance - 你钱包中持有的 WUSD 稳定币数量">{t('common.balance')}</div>
|
||||
<strong style={{ fontSize: '18px' }}>
|
||||
{balance !== undefined && decimals !== undefined
|
||||
? formatUnits(balance, decimals)
|
||||
? Number(formatUnits(balance, decimals)).toLocaleString()
|
||||
: '0'} {symbol || 'WUSD'}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
|
||||
{/* 铸造 */}
|
||||
<div>
|
||||
<div className="form-group">
|
||||
<label>{t('wusd.mintAmount')}</label>
|
||||
{/* 铸造和销毁 */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
|
||||
<div style={{ padding: '12px', background: '#fff', border: '1px solid #e0e0e0', borderRadius: '8px' }}>
|
||||
<div className="form-group" style={{ marginBottom: '8px' }}>
|
||||
<label style={{ fontSize: '13px', fontWeight: 500, cursor: 'help' }} title="铸造数量 Mint Amount - 输入要铸造的 WUSD 数量(仅 Owner 可用)">{t('wusd.mintAmount')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={mintAmount}
|
||||
@@ -273,10 +346,9 @@ export function WUSDPanel() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 销毁 */}
|
||||
<div>
|
||||
<div className="form-group">
|
||||
<label>{t('wusd.burnAmount')}</label>
|
||||
<div style={{ padding: '12px', background: '#fff', border: '1px solid #e0e0e0', borderRadius: '8px' }}>
|
||||
<div className="form-group" style={{ marginBottom: '8px' }}>
|
||||
<label style={{ fontSize: '13px', fontWeight: 500, cursor: 'help' }} title="销毁数量 Burn Amount - 输入要销毁的 WUSD 数量(仅 Owner 可用)">{t('wusd.burnAmount')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={burnAmount}
|
||||
@@ -296,13 +368,51 @@ export function WUSDPanel() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 转账功能 */}
|
||||
<div style={{ padding: '12px', background: '#fff', border: '1px solid #e0e0e0', borderRadius: '8px', marginBottom: '12px' }}>
|
||||
<h4 style={{ margin: '0 0 12px 0', fontSize: '14px', color: '#333', cursor: 'help' }} title="转账 Transfer - 将 WUSD 转移到其他钱包地址">{t('wusd.transfer')}</h4>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 120px', gap: '12px', alignItems: 'end' }}>
|
||||
<div>
|
||||
<div className="form-group" style={{ marginBottom: '8px' }}>
|
||||
<label style={{ fontSize: '12px', color: '#666' }}>{t('wusd.toAddress')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={transferTo}
|
||||
onChange={(e) => setTransferTo(e.target.value)}
|
||||
placeholder="0x..."
|
||||
className="input"
|
||||
style={{ fontSize: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '0' }}>
|
||||
<label style={{ fontSize: '12px', color: '#666' }}>{t('wusd.transferAmount')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={transferAmount}
|
||||
onChange={(e) => setTransferAmount(e.target.value)}
|
||||
placeholder={t('wusd.enterAmount')}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleTransfer}
|
||||
disabled={isProcessing || !transferTo || !transferAmount}
|
||||
className="btn btn-primary"
|
||||
style={{ height: '40px' }}
|
||||
>
|
||||
{isProcessing ? '...' : t('wusd.transfer')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 边界测试区域 */}
|
||||
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
|
||||
onClick={() => setShowBoundaryTest(!showBoundaryTest)}
|
||||
>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('test.boundaryTests')}</h4>
|
||||
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }} title="边界测试 Boundary Tests - 测试 WUSD 合约的边界条件和错误处理">{t('test.boundaryTests')}</h4>
|
||||
<span style={{ color: '#999', fontSize: '16px' }}>{showBoundaryTest ? '▼' : '▶'}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Gas配置 - 用于解决Arbitrum测试网gas预估问题
|
||||
// YT Asset Vault ABI - 用于读取 YT 代币本身的价格
|
||||
// 注意: YT代币需要实现 assetPrice() 接口(30位精度),供 YTPriceFeed 读取
|
||||
export const YT_ASSET_VAULT_ABI = [
|
||||
{
|
||||
inputs: [],
|
||||
@@ -15,14 +14,6 @@ export const YT_ASSET_VAULT_ABI = [
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// YTPriceFeed 期望的接口:assetPrice() - 30位精度
|
||||
{
|
||||
inputs: [],
|
||||
name: 'assetPrice',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
] as const
|
||||
|
||||
@@ -43,21 +34,23 @@ export const GAS_CONFIG = {
|
||||
}
|
||||
|
||||
export const CONTRACTS = {
|
||||
FACTORY: '0x6DaB73519DbaFf23F36FEd24110e2ef5Cfc8aAC9' as const,
|
||||
WUSD: '0x939cf46F7A4d05da2a37213E7379a8b04528F590' as const,
|
||||
// 12月18日新部署的合约地址
|
||||
FACTORY: '0x982716f32F10BCB5B5944c1473a8992354bF632b' as const,
|
||||
WUSD: '0x6d2bf81a631dFE19B2f348aE92cF6Ef41ca2DF98' as const, // fork测试用
|
||||
VAULTS: {
|
||||
// 旧金库地址(需要通过Factory.getAllVaults()动态获取新金库)
|
||||
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,
|
||||
YT_LP_TOKEN: '0x1b96F219E8aeE557DD8bD905a6c72cc64eA5BD7B' as const,
|
||||
YT_POOL_MANAGER: '0x14246886a1E1202cb6b5a2db793eF3359d536302' as const,
|
||||
YT_VAULT: '0x19982e5145ca5401A1084c0BF916c0E0bB343Af9' as const,
|
||||
USDY: '0x631Bd6834C50f6d2B07035c9253b4a19132E888c' as const,
|
||||
YT_PRICE_FEED: '0x0f2d930EE73972132E3a36b7eD6F709Af6E5B879' as const,
|
||||
// LP Pool contracts - 12月18日新部署
|
||||
YT_REWARD_ROUTER: '0x953758c02ec49F1f67fE2a8E3F67C434FeC5aB9d' as const,
|
||||
YT_LP_TOKEN: '0xf5206D958f692556603806A8f65bB106E23d1776' as const,
|
||||
YT_POOL_MANAGER: '0xe3068a25D6eEda551Cd12CC291813A4fe0e4AbB6' as const,
|
||||
YT_VAULT: '0xbC2e4f06601B92B3F430962a8f0a7E8c378ce54e' as const,
|
||||
USDY: '0x54551451E14D3d3418e4Aa9F31e9E8573fd37053' as const,
|
||||
YT_PRICE_FEED: '0x9364D3aF669886883C26EC0ff32000719491452A' as const,
|
||||
}
|
||||
|
||||
export const FACTORY_ABI = [
|
||||
@@ -265,6 +258,89 @@ export const FACTORY_ABI = [
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 批量创建金库
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: 'string[]', name: '_names', type: 'string[]' },
|
||||
{ internalType: 'string[]', name: '_symbols', type: 'string[]' },
|
||||
{ internalType: 'address[]', name: '_managers', type: 'address[]' },
|
||||
{ internalType: 'uint256[]', name: '_hardCaps', type: 'uint256[]' },
|
||||
{ internalType: 'address', name: '_wusd', type: 'address' },
|
||||
{ internalType: 'uint256[]', name: '_redemptionTimes', type: 'uint256[]' },
|
||||
{ internalType: 'uint256[]', name: '_initialWusdPrices', type: 'uint256[]' },
|
||||
{ internalType: 'uint256[]', name: '_initialYtPrices', type: 'uint256[]' }
|
||||
],
|
||||
name: 'createVaultBatch',
|
||||
outputs: [{ internalType: 'address[]', name: 'vaults', type: 'address[]' }],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 暂停金库
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_vault', type: 'address' }],
|
||||
name: 'pauseVault',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 批量暂停金库
|
||||
{
|
||||
inputs: [{ internalType: 'address[]', name: '_vaults', type: 'address[]' }],
|
||||
name: 'pauseVaultBatch',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 恢复金库
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_vault', type: 'address' }],
|
||||
name: 'unpauseVault',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 批量恢复金库
|
||||
{
|
||||
inputs: [{ internalType: 'address[]', name: '_vaults', type: 'address[]' }],
|
||||
name: 'unpauseVaultBatch',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 设置默认硬顶
|
||||
{
|
||||
inputs: [{ internalType: 'uint256', name: '_defaultHardCap', type: 'uint256' }],
|
||||
name: 'setDefaultHardCap',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 读取默认硬顶
|
||||
{
|
||||
inputs: [],
|
||||
name: 'defaultHardCap',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 获取金库详情(扩展)
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_vault', type: 'address' }],
|
||||
name: 'getVaultInfo',
|
||||
outputs: [
|
||||
{ internalType: 'bool', name: 'exists', type: 'bool' },
|
||||
{ internalType: 'uint256', name: 'totalAssets', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'idleAssets', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'managedAssets', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'totalSupply', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'hardCap', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'wusdPrice', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'ytPrice', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'nextRedemptionTime', type: 'uint256' }
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
] as const
|
||||
|
||||
@@ -452,13 +528,122 @@ export const VAULT_ABI = [
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 用户提交退出请求(排队机制),返回 requestId
|
||||
{
|
||||
inputs: [{ internalType: 'uint256', name: '_ytAmount', type: 'uint256' }],
|
||||
name: 'withdrawYT',
|
||||
outputs: [{ internalType: 'uint256', name: 'wusdAmount', type: 'uint256' }],
|
||||
outputs: [{ internalType: 'uint256', name: 'requestId', type: 'uint256' }],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 获取用户待处理的退出请求列表
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_user', type: 'address' }],
|
||||
name: 'getUserPendingRequests',
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{ internalType: 'address', name: 'user', type: 'address' },
|
||||
{ internalType: 'uint256', name: 'ytAmount', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'wusdAmount', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'requestTime', type: 'uint256' },
|
||||
{ internalType: 'bool', name: 'processed', type: 'bool' }
|
||||
],
|
||||
internalType: 'struct YTAssetVault.WithdrawRequest[]',
|
||||
name: 'pendingRequests',
|
||||
type: 'tuple[]'
|
||||
}
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 获取用户的所有请求ID
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_user', type: 'address' }],
|
||||
name: 'getUserRequestIds',
|
||||
outputs: [{ internalType: 'uint256[]', name: '', type: 'uint256[]' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 获取单个请求详情
|
||||
{
|
||||
inputs: [{ internalType: 'uint256', name: '_requestId', type: 'uint256' }],
|
||||
name: 'getRequestDetails',
|
||||
outputs: [
|
||||
{
|
||||
components: [
|
||||
{ internalType: 'address', name: 'user', type: 'address' },
|
||||
{ internalType: 'uint256', name: 'ytAmount', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'wusdAmount', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'requestTime', type: 'uint256' },
|
||||
{ internalType: 'bool', name: 'processed', type: 'bool' }
|
||||
],
|
||||
internalType: 'struct YTAssetVault.WithdrawRequest',
|
||||
name: 'request',
|
||||
type: 'tuple'
|
||||
}
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 获取待处理请求数量
|
||||
{
|
||||
inputs: [],
|
||||
name: 'getPendingRequestsCount',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 获取队列进度
|
||||
{
|
||||
inputs: [],
|
||||
name: 'getQueueProgress',
|
||||
outputs: [
|
||||
{ internalType: 'uint256', name: 'currentIndex', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'totalRequests', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'pendingRequests', type: 'uint256' }
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 待处理请求数量(状态变量)
|
||||
{
|
||||
inputs: [],
|
||||
name: 'pendingRequestsCount',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 请求ID计数器
|
||||
{
|
||||
inputs: [],
|
||||
name: 'requestIdCounter',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// 批量处理退出请求(Manager/Factory 调用)
|
||||
{
|
||||
inputs: [{ internalType: 'uint256', name: '_batchSize', type: 'uint256' }],
|
||||
name: 'processBatchWithdrawals',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 请求映射
|
||||
{
|
||||
inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
name: 'withdrawRequests',
|
||||
outputs: [
|
||||
{ internalType: 'address', name: 'user', type: 'address' },
|
||||
{ internalType: 'uint256', name: 'ytAmount', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'wusdAmount', type: 'uint256' },
|
||||
{ internalType: 'uint256', name: 'requestTime', type: 'uint256' },
|
||||
{ internalType: 'bool', name: 'processed', type: 'bool' }
|
||||
],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'wusdAddress',
|
||||
@@ -527,6 +712,14 @@ export const VAULT_ABI = [
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// 读取暂停状态
|
||||
{
|
||||
inputs: [],
|
||||
name: 'paused',
|
||||
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
}
|
||||
] as const
|
||||
|
||||
@@ -1134,10 +1327,10 @@ export const YT_VAULT_ABI = [
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// swap 开关
|
||||
// swap 开关 (合约实际使用 isSwapEnabled)
|
||||
{
|
||||
inputs: [],
|
||||
name: 'swapEnabled',
|
||||
name: 'isSwapEnabled',
|
||||
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
@@ -1167,3 +1360,94 @@ export const YT_VAULT_ABI = [
|
||||
type: 'function'
|
||||
}
|
||||
] as const
|
||||
|
||||
// USDY 合约 ABI (内部计价代币)
|
||||
export const USDY_ABI = [
|
||||
// 标准 ERC20 函数
|
||||
{
|
||||
inputs: [],
|
||||
name: 'name',
|
||||
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'symbol',
|
||||
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'decimals',
|
||||
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: 'totalSupply',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
|
||||
name: 'balanceOf',
|
||||
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// Owner
|
||||
{
|
||||
inputs: [],
|
||||
name: 'owner',
|
||||
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
// Vault 白名单管理
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||
name: 'vaults',
|
||||
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_vault', type: 'address' }],
|
||||
name: 'addVault',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: 'address', name: '_vault', type: 'address' }],
|
||||
name: 'removeVault',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
// mint/burn (仅白名单 Vault 可调用)
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: 'address', name: '_account', type: 'address' },
|
||||
{ internalType: 'uint256', name: '_amount', type: 'uint256' }
|
||||
],
|
||||
name: 'mint',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: 'address', name: '_account', type: 'address' },
|
||||
{ internalType: 'uint256', name: '_amount', type: 'uint256' }
|
||||
],
|
||||
name: 'burn',
|
||||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
}
|
||||
] as const
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export type TransactionType = 'mint' | 'burn' | 'buy' | 'sell' | 'approve' | 'create_vault' | 'update_price' | 'test' | 'addLiquidity' | 'removeLiquidity' | 'swap'
|
||||
export type TransactionType = 'mint' | 'burn' | 'buy' | 'sell' | 'approve' | 'create_vault' | 'update_price' | 'test' | 'addLiquidity' | 'removeLiquidity' | 'swap' | 'transfer'
|
||||
|
||||
export interface TransactionRecord {
|
||||
id: string
|
||||
|
||||
@@ -39,7 +39,13 @@
|
||||
"burn": "Burn",
|
||||
"minting": "Minting...",
|
||||
"confirming": "Confirming...",
|
||||
"mintSuccess": "Mint successful!"
|
||||
"mintSuccess": "Mint successful!",
|
||||
"owner": "Contract Owner",
|
||||
"totalSupply": "Total Supply",
|
||||
"transfer": "Transfer",
|
||||
"toAddress": "To Address",
|
||||
"transferAmount": "Transfer Amount",
|
||||
"invalidAddress": "Invalid address format"
|
||||
},
|
||||
"vault": {
|
||||
"title": "Vault Trading",
|
||||
@@ -87,7 +93,28 @@
|
||||
"toAddress": "To Address",
|
||||
"defaultSelf": "default: self",
|
||||
"approvedAmount": "Approved Amount",
|
||||
"needApprove": "Need Approve"
|
||||
"needApprove": "Need Approve",
|
||||
"vaultAddress": "Vault Address",
|
||||
"factoryAddress": "Factory Address",
|
||||
"wusdContract": "WUSD Contract",
|
||||
"pricePrecision": "Price Precision",
|
||||
"transferYt": "Transfer YT",
|
||||
"transfer": "Transfer",
|
||||
"invalidAddress": "Invalid address format",
|
||||
"queueWithdrawDesc": "Selling YT creates a withdrawal request and joins the queue. WUSD will be sent after admin processes the request.",
|
||||
"queueTotal": "Total Requests",
|
||||
"queuePending": "Pending",
|
||||
"queueProcessed": "Processed",
|
||||
"requestWithdraw": "Request Withdraw",
|
||||
"yourPendingRequests": "Your Pending Requests",
|
||||
"requestTime": "Request Time",
|
||||
"status": "Status",
|
||||
"processed": "Processed",
|
||||
"pending": "Pending",
|
||||
"processBatchWithdrawals": "Batch Process Withdrawals",
|
||||
"batchSize": "Batch Size",
|
||||
"processBatch": "Process Batch",
|
||||
"processBatchHint": "Process withdrawal requests from queue in order, sending WUSD to users"
|
||||
},
|
||||
"factory": {
|
||||
"title": "Factory Management",
|
||||
@@ -134,7 +161,18 @@
|
||||
"upgrade": "Upgrade",
|
||||
"batchUpgradeVault": "Batch Upgrade Vaults",
|
||||
"batchUpgrade": "Batch Upgrade",
|
||||
"selectInBatchSection": "select in batch section"
|
||||
"selectInBatchSection": "select in batch section",
|
||||
"batchPauseUnpause": "Batch Pause/Unpause Vaults",
|
||||
"pauseSelected": "Pause Selected",
|
||||
"unpauseSelected": "Unpause Selected",
|
||||
"pauseWarning": "Warning: Pausing a vault will block all buy/sell operations",
|
||||
"pauseVault": "Pause",
|
||||
"unpauseVault": "Unpause",
|
||||
"paused": "Paused",
|
||||
"active": "Active",
|
||||
"batchCreateVaults": "Batch Create Vaults",
|
||||
"addVaultRow": "Add Vault",
|
||||
"createBatch": "Create Batch"
|
||||
},
|
||||
"language": {
|
||||
"en": "English",
|
||||
|
||||
@@ -39,7 +39,13 @@
|
||||
"burn": "销毁",
|
||||
"minting": "铸造中...",
|
||||
"confirming": "确认中...",
|
||||
"mintSuccess": "铸造成功!"
|
||||
"mintSuccess": "铸造成功!",
|
||||
"owner": "合约所有者",
|
||||
"totalSupply": "总供应量",
|
||||
"transfer": "转账",
|
||||
"toAddress": "接收地址",
|
||||
"transferAmount": "转账数量",
|
||||
"invalidAddress": "无效的地址格式"
|
||||
},
|
||||
"vault": {
|
||||
"title": "金库交易",
|
||||
@@ -87,7 +93,28 @@
|
||||
"toAddress": "接收地址",
|
||||
"defaultSelf": "默认为自己",
|
||||
"approvedAmount": "已授权额度",
|
||||
"needApprove": "需要授权"
|
||||
"needApprove": "需要授权",
|
||||
"vaultAddress": "金库地址",
|
||||
"factoryAddress": "工厂地址",
|
||||
"wusdContract": "WUSD 合约",
|
||||
"pricePrecision": "价格精度",
|
||||
"transferYt": "转账 YT",
|
||||
"transfer": "转账",
|
||||
"invalidAddress": "无效的地址格式",
|
||||
"queueWithdrawDesc": "卖出 YT 将创建退出请求并加入队列,等待管理员处理后发放 WUSD。",
|
||||
"queueTotal": "总请求数",
|
||||
"queuePending": "待处理",
|
||||
"queueProcessed": "已处理",
|
||||
"requestWithdraw": "申请退出",
|
||||
"yourPendingRequests": "你的待处理请求",
|
||||
"requestTime": "请求时间",
|
||||
"status": "状态",
|
||||
"processed": "已处理",
|
||||
"pending": "等待中",
|
||||
"processBatchWithdrawals": "批量处理退出",
|
||||
"batchSize": "批量大小",
|
||||
"processBatch": "处理退出",
|
||||
"processBatchHint": "从队列中按顺序处理指定数量的退出请求,向用户发送 WUSD"
|
||||
},
|
||||
"factory": {
|
||||
"title": "工厂管理",
|
||||
@@ -134,7 +161,18 @@
|
||||
"upgrade": "升级",
|
||||
"batchUpgradeVault": "批量升级金库",
|
||||
"batchUpgrade": "批量升级",
|
||||
"selectInBatchSection": "在批量操作区选择"
|
||||
"selectInBatchSection": "在批量操作区选择",
|
||||
"batchPauseUnpause": "批量暂停/恢复金库",
|
||||
"pauseSelected": "暂停所选金库",
|
||||
"unpauseSelected": "恢复所选金库",
|
||||
"pauseWarning": "警告: 暂停金库将阻止所有买入/卖出操作",
|
||||
"pauseVault": "暂停",
|
||||
"unpauseVault": "恢复",
|
||||
"paused": "已暂停",
|
||||
"active": "运行中",
|
||||
"batchCreateVaults": "批量创建金库",
|
||||
"addVaultRow": "添加金库",
|
||||
"createBatch": "批量创建"
|
||||
},
|
||||
"language": {
|
||||
"en": "英文",
|
||||
|
||||
Reference in New Issue
Block a user