feat: 完善工厂和金库管理功能
Factory 功能完善: - 添加 setDefaultHardCap 设置默认硬顶 - 添加 vaultImplementation 显示 - 添加批量操作: updateVaultPricesBatch, setHardCapBatch, setVaultNextRedemptionTimeBatch - 添加高级功能: setVaultImplementation, upgradeVault, upgradeVaultBatch - Owner 配置和批量操作区域 UI Vault 功能完善: - 添加 Manager 面板 (depositManagedAssets/withdrawForManagement) - 修复 Manager 存入托管资产需要授权的问题 - 添加 managedAssets 读取和显示 UI 优化: - 统一颜色方案 - 边界测试卡片式布局 - 可折叠区域统一样式 WUSD 功能: - 添加销毁功能 - 修复 gas 配置问题 翻译更新: - 添加所有新功能的中英文翻译 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Markdown files
|
||||||
|
*.md
|
||||||
|
|
||||||
|
# Node modules
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
dist-ssr/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.sw?
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Claude plans
|
||||||
|
.claude/
|
||||||
@@ -1,3 +1,33 @@
|
|||||||
|
/* 统一颜色变量 */
|
||||||
|
:root {
|
||||||
|
/* 主色 */
|
||||||
|
--color-primary: #1976d2;
|
||||||
|
--color-primary-dark: #1565c0;
|
||||||
|
--color-primary-light: #e3f2fd;
|
||||||
|
|
||||||
|
/* 文字 */
|
||||||
|
--color-text: #333;
|
||||||
|
--color-text-secondary: #666;
|
||||||
|
--color-text-muted: #999;
|
||||||
|
|
||||||
|
/* 背景 */
|
||||||
|
--color-bg: #f5f5f5;
|
||||||
|
--color-bg-card: #fff;
|
||||||
|
--color-bg-section: #f8f9fa;
|
||||||
|
|
||||||
|
/* 边框 */
|
||||||
|
--color-border: #e0e0e0;
|
||||||
|
--color-border-light: #eee;
|
||||||
|
|
||||||
|
/* 状态色 - 仅用于状态指示 */
|
||||||
|
--color-success: #4caf50;
|
||||||
|
--color-success-bg: #e8f5e9;
|
||||||
|
--color-warning: #ff9800;
|
||||||
|
--color-warning-bg: #fff3e0;
|
||||||
|
--color-error: #f44336;
|
||||||
|
--color-error-bg: #ffebee;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -6,8 +36,8 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||||
background-color: #f5f5f5;
|
background-color: var(--color-bg);
|
||||||
color: #333;
|
color: var(--color-text);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,42 +483,62 @@ body {
|
|||||||
|
|
||||||
.test-grid {
|
.test-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-card {
|
.test-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid #e0e0e0;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
padding: 14px;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-header {
|
.test-card-left {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
gap: 2px;
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-name {
|
.test-card-left .test-name {
|
||||||
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
color: #333;
|
||||||
color: #1a1a1a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-error {
|
.test-card-left .test-error {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card-left .test-desc {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card-right .btn {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #f57c00;
|
padding: 4px 12px;
|
||||||
background: #fff3e0;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-desc {
|
.test-desc {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: #666;
|
color: #999;
|
||||||
margin-bottom: 12px;
|
margin: 0;
|
||||||
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-actions {
|
.test-actions {
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ export function FactoryPanel() {
|
|||||||
ytPrice: '',
|
ytPrice: '',
|
||||||
})
|
})
|
||||||
const [showPermissionTest, setShowPermissionTest] = useState(false)
|
const [showPermissionTest, setShowPermissionTest] = useState(false)
|
||||||
|
const [showOwnerConfig, setShowOwnerConfig] = useState(false)
|
||||||
|
const [newDefaultHardCap, setNewDefaultHardCap] = useState('')
|
||||||
|
const [showBatchOps, setShowBatchOps] = useState(false)
|
||||||
|
const [batchPriceForm, setBatchPriceForm] = useState({ wusdPrice: '1', ytPrice: '1' })
|
||||||
|
const [batchHardCapForm, setBatchHardCapForm] = useState('')
|
||||||
|
const [selectedVaultsForBatch, setSelectedVaultsForBatch] = useState<string[]>([])
|
||||||
|
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||||
|
const [newImplementation, setNewImplementation] = useState('')
|
||||||
|
const [upgradeVaultAddr, setUpgradeVaultAddr] = useState('')
|
||||||
|
const [upgradeImplAddr, setUpgradeImplAddr] = useState('')
|
||||||
|
const [batchRedemptionTime, setBatchRedemptionTime] = useState('')
|
||||||
|
|
||||||
const { data: allVaults, refetch: refetchVaults } = useReadContract({
|
const { data: allVaults, refetch: refetchVaults } = useReadContract({
|
||||||
address: CONTRACTS.FACTORY,
|
address: CONTRACTS.FACTORY,
|
||||||
@@ -41,12 +52,18 @@ export function FactoryPanel() {
|
|||||||
functionName: 'owner',
|
functionName: 'owner',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: defaultHardCap } = useReadContract({
|
const { data: defaultHardCap, refetch: refetchDefaultHardCap } = useReadContract({
|
||||||
address: CONTRACTS.FACTORY,
|
address: CONTRACTS.FACTORY,
|
||||||
abi: FACTORY_ABI,
|
abi: FACTORY_ABI,
|
||||||
functionName: 'defaultHardCap',
|
functionName: 'defaultHardCap',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { data: vaultImplementation } = useReadContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'vaultImplementation',
|
||||||
|
})
|
||||||
|
|
||||||
const { writeContract, data: hash, isPending, reset, error: writeError, status: writeStatus } = useWriteContract()
|
const { writeContract, data: hash, isPending, reset, error: writeError, status: writeStatus } = useWriteContract()
|
||||||
|
|
||||||
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
|
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
|
||||||
@@ -56,6 +73,7 @@ export function FactoryPanel() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
refetchVaults()
|
refetchVaults()
|
||||||
|
refetchDefaultHardCap()
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
}, [isSuccess])
|
}, [isSuccess])
|
||||||
@@ -108,6 +126,125 @@ export function FactoryPanel() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSetDefaultHardCap = () => {
|
||||||
|
if (!newDefaultHardCap) return
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setDefaultHardCap',
|
||||||
|
args: [parseUnits(newDefaultHardCap, 18)],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新价格
|
||||||
|
const handleBatchUpdatePrices = () => {
|
||||||
|
if (selectedVaultsForBatch.length === 0) return
|
||||||
|
const wusdPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.wusdPrice, 18))
|
||||||
|
const ytPrices = selectedVaultsForBatch.map(() => parseUnits(batchPriceForm.ytPrice, 18))
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'updateVaultPricesBatch',
|
||||||
|
args: [selectedVaultsForBatch as `0x${string}`[], wusdPrices, ytPrices],
|
||||||
|
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量设置硬顶
|
||||||
|
const handleBatchSetHardCap = () => {
|
||||||
|
if (selectedVaultsForBatch.length === 0 || !batchHardCapForm) return
|
||||||
|
const hardCaps = selectedVaultsForBatch.map(() => parseUnits(batchHardCapForm, 18))
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setHardCapBatch',
|
||||||
|
args: [selectedVaultsForBatch as `0x${string}`[], hardCaps],
|
||||||
|
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换选中金库
|
||||||
|
const toggleVaultSelection = (vaultAddr: string) => {
|
||||||
|
setSelectedVaultsForBatch(prev =>
|
||||||
|
prev.includes(vaultAddr)
|
||||||
|
? prev.filter(v => v !== vaultAddr)
|
||||||
|
: [...prev, vaultAddr]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选/取消全选
|
||||||
|
const toggleSelectAll = () => {
|
||||||
|
if (allVaults && selectedVaultsForBatch.length === allVaults.length) {
|
||||||
|
setSelectedVaultsForBatch([])
|
||||||
|
} else if (allVaults) {
|
||||||
|
setSelectedVaultsForBatch([...allVaults])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量设置赎回时间
|
||||||
|
const handleBatchSetRedemptionTime = () => {
|
||||||
|
if (selectedVaultsForBatch.length === 0 || !batchRedemptionTime) return
|
||||||
|
const timestamp = BigInt(Math.floor(new Date(batchRedemptionTime).getTime() / 1000))
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setVaultNextRedemptionTimeBatch',
|
||||||
|
args: [selectedVaultsForBatch as `0x${string}`[], timestamp],
|
||||||
|
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置金库实现地址
|
||||||
|
const handleSetVaultImplementation = () => {
|
||||||
|
if (!newImplementation) return
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setVaultImplementation',
|
||||||
|
args: [newImplementation as `0x${string}`],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级单个金库
|
||||||
|
const handleUpgradeVault = () => {
|
||||||
|
if (!upgradeVaultAddr || !upgradeImplAddr) return
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'upgradeVault',
|
||||||
|
args: [upgradeVaultAddr as `0x${string}`, upgradeImplAddr as `0x${string}`],
|
||||||
|
gas: GAS_CONFIG.COMPLEX,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量升级金库
|
||||||
|
const handleBatchUpgradeVault = () => {
|
||||||
|
if (selectedVaultsForBatch.length === 0 || !upgradeImplAddr) return
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'upgradeVaultBatch',
|
||||||
|
args: [selectedVaultsForBatch as `0x${string}`[], upgradeImplAddr as `0x${string}`],
|
||||||
|
gas: GAS_CONFIG.VERY_COMPLEX,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 权限测试函数
|
// 权限测试函数
|
||||||
const runPermissionTest = (testType: string) => {
|
const runPermissionTest = (testType: string) => {
|
||||||
const testVault = allVaults && allVaults.length > 0 ? allVaults[0] : CONTRACTS.VAULTS.YT_A
|
const testVault = allVaults && allVaults.length > 0 ? allVaults[0] : CONTRACTS.VAULTS.YT_A
|
||||||
@@ -168,6 +305,10 @@ export function FactoryPanel() {
|
|||||||
<span>{t('factory.owner')}:</span>
|
<span>{t('factory.owner')}:</span>
|
||||||
<code>{owner || '-'}</code>
|
<code>{owner || '-'}</code>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('factory.vaultImpl')}:</span>
|
||||||
|
<code style={{ fontSize: '10px' }}>{vaultImplementation || '-'}</code>
|
||||||
|
</div>
|
||||||
<div className="info-row">
|
<div className="info-row">
|
||||||
<span>{t('factory.defaultHardCap')}:</span>
|
<span>{t('factory.defaultHardCap')}:</span>
|
||||||
<strong>{defaultHardCap ? formatUnits(defaultHardCap, 18) : '0'}</strong>
|
<strong>{defaultHardCap ? formatUnits(defaultHardCap, 18) : '0'}</strong>
|
||||||
@@ -332,14 +473,290 @@ export function FactoryPanel() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Owner 配置区域 */}
|
||||||
|
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||||
|
<div
|
||||||
|
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>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showOwnerConfig ? '▼' : '▶'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showOwnerConfig && (
|
||||||
|
<div style={{ marginTop: '10px', padding: '12px', background: '#fff', borderRadius: '6px' }}>
|
||||||
|
{/* 设置默认硬顶 */}
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '6px', fontSize: '13px', fontWeight: 500 }}>
|
||||||
|
{t('factory.setDefaultHardCap')}
|
||||||
|
</label>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={newDefaultHardCap}
|
||||||
|
onChange={(e) => setNewDefaultHardCap(e.target.value)}
|
||||||
|
placeholder={defaultHardCap ? formatUnits(defaultHardCap, 18) : '1000000'}
|
||||||
|
className="input"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSetDefaultHardCap}
|
||||||
|
disabled={isProcessing || !newDefaultHardCap}
|
||||||
|
className="btn btn-primary"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('common.confirm')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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={() => setShowBatchOps(!showBatchOps)}
|
||||||
|
>
|
||||||
|
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('factory.batchOperations')}</h4>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showBatchOps ? '▼' : '▶'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showBatchOps && (
|
||||||
|
<div style={{ marginTop: '10px', padding: '12px', background: '#fff', borderRadius: '6px' }}>
|
||||||
|
{/* 金库选择 */}
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||||
|
<label style={{ fontSize: '13px', fontWeight: 500 }}>{t('factory.selectVaultsForBatch')}</label>
|
||||||
|
<button onClick={toggleSelectAll} className="btn btn-secondary btn-sm">
|
||||||
|
{allVaults && selectedVaultsForBatch.length === allVaults.length ? t('factory.deselectAll') : t('factory.selectAll')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
|
||||||
|
{allVaults?.map((vault, index) => (
|
||||||
|
<label key={index} style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
padding: '4px 8px',
|
||||||
|
background: selectedVaultsForBatch.includes(vault) ? '#e3f2fd' : '#f8f9fa',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '12px',
|
||||||
|
border: selectedVaultsForBatch.includes(vault) ? '1px solid #2196f3' : '1px solid #e0e0e0'
|
||||||
|
}}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedVaultsForBatch.includes(vault)}
|
||||||
|
onChange={() => toggleVaultSelection(vault)}
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
/>
|
||||||
|
Vault {index + 1}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '11px', color: '#666', marginTop: '4px' }}>
|
||||||
|
{t('factory.selectedCount')}: {selectedVaultsForBatch.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 批量更新价格 */}
|
||||||
|
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.batchUpdatePrices')}</h5>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr auto', gap: '8px', alignItems: 'end' }}>
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: '11px', color: '#666' }}>{t('vault.wusdPrice')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={batchPriceForm.wusdPrice}
|
||||||
|
onChange={(e) => setBatchPriceForm({ ...batchPriceForm, wusdPrice: e.target.value })}
|
||||||
|
className="input"
|
||||||
|
style={{ fontSize: '13px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style={{ fontSize: '11px', color: '#666' }}>{t('vault.ytPrice')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={batchPriceForm.ytPrice}
|
||||||
|
onChange={(e) => setBatchPriceForm({ ...batchPriceForm, ytPrice: e.target.value })}
|
||||||
|
className="input"
|
||||||
|
style={{ fontSize: '13px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleBatchUpdatePrices}
|
||||||
|
disabled={isProcessing || selectedVaultsForBatch.length === 0}
|
||||||
|
className="btn btn-primary btn-sm"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('factory.update')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 批量设置硬顶 */}
|
||||||
|
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.batchSetHardCap')}</h5>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={batchHardCapForm}
|
||||||
|
onChange={(e) => setBatchHardCapForm(e.target.value)}
|
||||||
|
placeholder="100000"
|
||||||
|
className="input"
|
||||||
|
style={{ flex: 1, fontSize: '13px' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleBatchSetHardCap}
|
||||||
|
disabled={isProcessing || selectedVaultsForBatch.length === 0 || !batchHardCapForm}
|
||||||
|
className="btn btn-primary btn-sm"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('vault.setHardCap')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 批量设置赎回时间 */}
|
||||||
|
<div style={{ 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
|
||||||
|
type="datetime-local"
|
||||||
|
value={batchRedemptionTime}
|
||||||
|
onChange={(e) => setBatchRedemptionTime(e.target.value)}
|
||||||
|
className="input"
|
||||||
|
style={{ flex: 1, fontSize: '13px' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleBatchSetRedemptionTime}
|
||||||
|
disabled={isProcessing || selectedVaultsForBatch.length === 0 || !batchRedemptionTime}
|
||||||
|
className="btn btn-primary btn-sm"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('common.confirm')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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={() => setShowAdvanced(!showAdvanced)}
|
||||||
|
>
|
||||||
|
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('factory.advancedFunctions')}</h4>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showAdvanced ? '▼' : '▶'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showAdvanced && (
|
||||||
|
<div style={{ marginTop: '10px', padding: '12px', background: '#fff', borderRadius: '6px' }}>
|
||||||
|
{/* 警告提示 */}
|
||||||
|
<div style={{ marginBottom: '16px', padding: '8px', background: '#fff3e0', borderRadius: '4px', fontSize: '12px', color: '#e65100' }}>
|
||||||
|
{t('factory.advancedWarning')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 设置金库实现 */}
|
||||||
|
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.setVaultImplementation')}</h5>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newImplementation}
|
||||||
|
onChange={(e) => setNewImplementation(e.target.value)}
|
||||||
|
placeholder="0x..."
|
||||||
|
className="input"
|
||||||
|
style={{ flex: 1, fontSize: '12px' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSetVaultImplementation}
|
||||||
|
disabled={isProcessing || !newImplementation}
|
||||||
|
className="btn btn-secondary btn-sm"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('common.confirm')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '11px', color: '#666', marginTop: '4px' }}>
|
||||||
|
{t('factory.currentImpl')}: <code style={{ fontSize: '10px' }}>{vaultImplementation || '-'}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 升级单个金库 */}
|
||||||
|
<div style={{ marginBottom: '16px', padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.upgradeVault')}</h5>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<select
|
||||||
|
value={upgradeVaultAddr}
|
||||||
|
onChange={(e) => setUpgradeVaultAddr(e.target.value)}
|
||||||
|
className="input"
|
||||||
|
style={{ fontSize: '12px', marginBottom: '8px' }}
|
||||||
|
>
|
||||||
|
<option value="">{t('factory.selectVault')}</option>
|
||||||
|
{allVaults?.map((vault, index) => (
|
||||||
|
<option key={index} value={vault}>
|
||||||
|
Vault {index + 1}: {vault.slice(0, 10)}...
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={upgradeImplAddr}
|
||||||
|
onChange={(e) => setUpgradeImplAddr(e.target.value)}
|
||||||
|
placeholder={t('factory.newImplAddress')}
|
||||||
|
className="input"
|
||||||
|
style={{ fontSize: '12px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleUpgradeVault}
|
||||||
|
disabled={isProcessing || !upgradeVaultAddr || !upgradeImplAddr}
|
||||||
|
className="btn btn-secondary btn-sm"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('factory.upgrade')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 批量升级金库 */}
|
||||||
|
<div style={{ padding: '10px', background: '#f8f9fa', borderRadius: '6px' }}>
|
||||||
|
<h5 style={{ margin: '0 0 10px 0', fontSize: '13px' }}>{t('factory.batchUpgradeVault')}</h5>
|
||||||
|
<div style={{ fontSize: '11px', color: '#666', marginBottom: '8px' }}>
|
||||||
|
{t('factory.selectedCount')}: {selectedVaultsForBatch.length} ({t('factory.selectInBatchSection')})
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={upgradeImplAddr}
|
||||||
|
onChange={(e) => setUpgradeImplAddr(e.target.value)}
|
||||||
|
placeholder={t('factory.newImplAddress')}
|
||||||
|
className="input"
|
||||||
|
style={{ flex: 1, fontSize: '12px' }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleBatchUpgradeVault}
|
||||||
|
disabled={isProcessing || selectedVaultsForBatch.length === 0 || !upgradeImplAddr}
|
||||||
|
className="btn btn-secondary btn-sm"
|
||||||
|
>
|
||||||
|
{isProcessing ? '...' : t('factory.batchUpgrade')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 权限测试区域 */}
|
{/* 权限测试区域 */}
|
||||||
<div className="section">
|
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||||
<div className="section-header" onClick={() => setShowPermissionTest(!showPermissionTest)} style={{ cursor: 'pointer' }}>
|
<div
|
||||||
<h3>{t('test.permissionTests')} {showPermissionTest ? '[-]' : '[+]'}</h3>
|
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>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showPermissionTest ? '▼' : '▶'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showPermissionTest && (
|
{showPermissionTest && (
|
||||||
<>
|
<div style={{ marginTop: '10px' }}>
|
||||||
<div className="test-hint">
|
<div className="test-hint">
|
||||||
{t('test.permissionHint')}
|
{t('test.permissionHint')}
|
||||||
{isOwner && <span style={{ color: '#2e7d32', marginLeft: '8px' }}>(You are Owner)</span>}
|
{isOwner && <span style={{ color: '#2e7d32', marginLeft: '8px' }}>(You are Owner)</span>}
|
||||||
@@ -347,28 +764,27 @@ export function FactoryPanel() {
|
|||||||
|
|
||||||
<div className="test-grid">
|
<div className="test-grid">
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.updatePriceNotOwner')}</span>
|
<span className="test-name">{t('test.updatePriceNotOwner')}</span>
|
||||||
<span className="test-error">OwnableUnauthorizedAccount</span>
|
<span className="test-error">Unauthorized</span>
|
||||||
|
<p className="test-desc">{t('test.updatePriceNotOwnerDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runPermissionTest('update_price_not_owner')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.updatePriceNotOwnerDesc')}</p>
|
|
||||||
<button onClick={() => runPermissionTest('update_price_not_owner')} disabled={isProcessing} className="btn btn-danger btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.setManagerNotOwner')}</span>
|
<span className="test-name">{t('test.setManagerNotOwner')}</span>
|
||||||
<span className="test-error">OwnableUnauthorizedAccount</span>
|
<span className="test-error">Unauthorized</span>
|
||||||
|
<p className="test-desc">{t('test.setManagerNotOwnerDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runPermissionTest('set_manager_not_owner')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.setManagerNotOwnerDesc')}</p>
|
|
||||||
<button onClick={() => runPermissionTest('set_manager_not_owner')} disabled={isProcessing} className="btn btn-danger btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,19 @@ export function VaultPanel() {
|
|||||||
const [testResult, setTestResult] = useState<{ type: 'success' | 'error' | 'pending', msg: string } | null>(null)
|
const [testResult, setTestResult] = useState<{ type: 'success' | 'error' | 'pending', msg: string } | null>(null)
|
||||||
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
||||||
|
|
||||||
|
// 管理员配置状态
|
||||||
|
const [showAdminConfig, setShowAdminConfig] = useState(false)
|
||||||
|
const [priceUpdateForm, setPriceUpdateForm] = useState({ wusdPrice: '1', ytPrice: '1' })
|
||||||
|
const [hardCapForm, setHardCapForm] = useState('')
|
||||||
|
const [redemptionTimeForm, setRedemptionTimeForm] = useState('')
|
||||||
|
const [managerForm, setManagerForm] = useState('')
|
||||||
|
|
||||||
|
// Manager 资产管理状态
|
||||||
|
const [showManagerPanel, setShowManagerPanel] = useState(false)
|
||||||
|
const [depositManagedAmount, setDepositManagedAmount] = useState('')
|
||||||
|
const [withdrawManagedAmount, setWithdrawManagedAmount] = useState('')
|
||||||
|
const [withdrawToAddress, setWithdrawToAddress] = useState('')
|
||||||
|
|
||||||
// 从合约动态读取所有金库地址
|
// 从合约动态读取所有金库地址
|
||||||
const { data: allVaultsData } = useReadContract({
|
const { data: allVaultsData } = useReadContract({
|
||||||
address: CONTRACTS.FACTORY,
|
address: CONTRACTS.FACTORY,
|
||||||
@@ -139,6 +152,21 @@ export function VaultPanel() {
|
|||||||
functionName: 'manager',
|
functionName: 'manager',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 读取 managedAssets (Manager 管理的资产)
|
||||||
|
const { data: managedAssets, refetch: refetchManagedAssets } = useReadContract({
|
||||||
|
address: selectedVault.address as `0x${string}`,
|
||||||
|
abi: VAULT_ABI,
|
||||||
|
functionName: 'managedAssets',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 读取 Manager 对 Vault 的 WUSD 授权额度 (用于 depositManagedAssets)
|
||||||
|
const { data: managerWusdAllowance, refetch: refetchManagerAllowance } = useReadContract({
|
||||||
|
address: CONTRACTS.WUSD,
|
||||||
|
abi: WUSD_ABI,
|
||||||
|
functionName: 'allowance',
|
||||||
|
args: address ? [address, selectedVault.address as `0x${string}`] : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
const { data: factoryOwner } = useReadContract({
|
const { data: factoryOwner } = useReadContract({
|
||||||
address: CONTRACTS.FACTORY,
|
address: CONTRACTS.FACTORY,
|
||||||
abi: FACTORY_ABI,
|
abi: FACTORY_ABI,
|
||||||
@@ -192,8 +220,12 @@ export function VaultPanel() {
|
|||||||
refetchYtBalance()
|
refetchYtBalance()
|
||||||
refetchWusdBalance()
|
refetchWusdBalance()
|
||||||
refetchAllowance()
|
refetchAllowance()
|
||||||
|
refetchManagedAssets()
|
||||||
|
refetchManagerAllowance()
|
||||||
setBuyAmount('')
|
setBuyAmount('')
|
||||||
setSellAmount('')
|
setSellAmount('')
|
||||||
|
setDepositManagedAmount('')
|
||||||
|
setWithdrawManagedAmount('')
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
}, [isSuccess])
|
}, [isSuccess])
|
||||||
@@ -333,6 +365,125 @@ export function VaultPanel() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 管理员功能 =====
|
||||||
|
|
||||||
|
// 更新金库价格 (Factory.updateVaultPrices)
|
||||||
|
const handleUpdatePrices = () => {
|
||||||
|
if (!address) return
|
||||||
|
const wusdPrice = parseUnits(priceUpdateForm.wusdPrice, 18)
|
||||||
|
const ytPrice = parseUnits(priceUpdateForm.ytPrice, 18)
|
||||||
|
recordTx('test', `${priceUpdateForm.wusdPrice}/${priceUpdateForm.ytPrice}`, 'UpdatePrices')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'updateVaultPrices',
|
||||||
|
args: [selectedVault.address as `0x${string}`, wusdPrice, ytPrice],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置硬顶 (Factory.setHardCap)
|
||||||
|
const handleSetHardCap = () => {
|
||||||
|
if (!address || !hardCapForm) return
|
||||||
|
const hardCap = parseUnits(hardCapForm, 18)
|
||||||
|
recordTx('test', hardCapForm, 'SetHardCap')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setHardCap',
|
||||||
|
args: [selectedVault.address as `0x${string}`, hardCap],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置赎回时间 (Factory.setVaultNextRedemptionTime)
|
||||||
|
const handleSetRedemptionTime = () => {
|
||||||
|
if (!address || !redemptionTimeForm) return
|
||||||
|
const timestamp = BigInt(Math.floor(new Date(redemptionTimeForm).getTime() / 1000))
|
||||||
|
recordTx('test', redemptionTimeForm, 'SetRedemptionTime')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setVaultNextRedemptionTime',
|
||||||
|
args: [selectedVault.address as `0x${string}`, timestamp],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置管理员 (Factory.setVaultManager)
|
||||||
|
const handleSetManager = () => {
|
||||||
|
if (!address || !managerForm) return
|
||||||
|
recordTx('test', managerForm.slice(0, 10) + '...', 'SetManager')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.FACTORY,
|
||||||
|
abi: FACTORY_ABI,
|
||||||
|
functionName: 'setVaultManager',
|
||||||
|
args: [selectedVault.address as `0x${string}`, managerForm as `0x${string}`],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Manager 资产管理功能 =====
|
||||||
|
|
||||||
|
// Manager 授权 WUSD 给 Vault (用于 depositManagedAssets)
|
||||||
|
const handleManagerApproveWusd = () => {
|
||||||
|
if (!address || !depositManagedAmount) return
|
||||||
|
recordTx('approve', depositManagedAmount, 'WUSD')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.WUSD,
|
||||||
|
abi: WUSD_ABI,
|
||||||
|
functionName: 'approve',
|
||||||
|
args: [selectedVault.address as `0x${string}`, parseUnits(depositManagedAmount, 18)],
|
||||||
|
gas: GAS_CONFIG.SIMPLE,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存入托管资产 (Manager 将资产从外部存回金库)
|
||||||
|
const handleDepositManagedAssets = () => {
|
||||||
|
if (!address || !depositManagedAmount) return
|
||||||
|
recordTx('test', depositManagedAmount, 'DepositManaged')
|
||||||
|
writeContract({
|
||||||
|
address: selectedVault.address as `0x${string}`,
|
||||||
|
abi: VAULT_ABI,
|
||||||
|
functionName: 'depositManagedAssets',
|
||||||
|
args: [parseUnits(depositManagedAmount, 18)],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 Manager 是否需要授权
|
||||||
|
const managerNeedsApproval = depositManagedAmount
|
||||||
|
? (managerWusdAllowance === undefined || parseUnits(depositManagedAmount, 18) > managerWusdAllowance)
|
||||||
|
: false
|
||||||
|
|
||||||
|
// 提取托管资产 (Manager 从金库提取资产进行管理)
|
||||||
|
const handleWithdrawForManagement = () => {
|
||||||
|
if (!address || !withdrawManagedAmount) return
|
||||||
|
const toAddress = withdrawToAddress || address
|
||||||
|
recordTx('test', withdrawManagedAmount, 'WithdrawManaged')
|
||||||
|
writeContract({
|
||||||
|
address: selectedVault.address as `0x${string}`,
|
||||||
|
abi: VAULT_ABI,
|
||||||
|
functionName: 'withdrawForManagement',
|
||||||
|
args: [toAddress as `0x${string}`, parseUnits(withdrawManagedAmount, 18)],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 边界测试是否正在运行
|
// 边界测试是否正在运行
|
||||||
const [isTestRunning, setIsTestRunning] = useState(false)
|
const [isTestRunning, setIsTestRunning] = useState(false)
|
||||||
const testTypeRef = useRef<string | null>(null)
|
const testTypeRef = useRef<string | null>(null)
|
||||||
@@ -459,8 +610,9 @@ export function VaultPanel() {
|
|||||||
}
|
}
|
||||||
}, [isSuccess, isTestRunning])
|
}, [isSuccess, isTestRunning])
|
||||||
|
|
||||||
const needsApproval = buyAmount && wusdAllowance !== undefined
|
// 修复: 当 wusdAllowance 未加载时,假设需要授权 (安全起见)
|
||||||
? parseUnits(buyAmount, 18) > wusdAllowance
|
const needsApproval = buyAmount
|
||||||
|
? (wusdAllowance === undefined || parseUnits(buyAmount, 18) > wusdAllowance)
|
||||||
: false
|
: false
|
||||||
|
|
||||||
// 格式化剩余时间
|
// 格式化剩余时间
|
||||||
@@ -611,6 +763,11 @@ export function VaultPanel() {
|
|||||||
{t('vault.youWillReceive')}: <strong>{formatUnits(previewBuyAmount, 18)} YT</strong>
|
{t('vault.youWillReceive')}: <strong>{formatUnits(previewBuyAmount, 18)} YT</strong>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Debug: 显示授权状态 */}
|
||||||
|
<div className="debug-info" style={{ fontSize: '12px', color: '#999', marginBottom: '8px' }}>
|
||||||
|
授权额度: {wusdAllowance !== undefined ? formatUnits(wusdAllowance, 18) : 'loading...'} WUSD
|
||||||
|
{buyAmount && ` | 需要: ${buyAmount} | 需授权: ${needsApproval ? '是' : '否'}`}
|
||||||
|
</div>
|
||||||
{needsApproval ? (
|
{needsApproval ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleApprove}
|
onClick={handleApprove}
|
||||||
@@ -670,14 +827,230 @@ export function VaultPanel() {
|
|||||||
</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={() => setShowAdminConfig(!showAdminConfig)}
|
||||||
|
>
|
||||||
|
<h4 style={{ margin: 0, color: '#666', fontSize: '13px' }}>{t('vault.adminConfig')}</h4>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showAdminConfig ? '▼' : '▶'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showAdminConfig && (
|
||||||
|
<div style={{ marginTop: '10px', padding: '12px', background: '#fff', borderRadius: '6px' }}>
|
||||||
|
<div className="admin-info" style={{ marginBottom: '16px', padding: '8px', background: '#fff3e0', borderRadius: '4px', fontSize: '12px' }}>
|
||||||
|
<p style={{ margin: 0 }}>Factory Owner: <code style={{ fontSize: '10px' }}>{factoryOwner || 'Loading...'}</code></p>
|
||||||
|
<p style={{ margin: '4px 0 0 0' }}>Vault Manager: <code style={{ fontSize: '10px' }}>{vaultManager || 'Loading...'}</code></p>
|
||||||
|
<p style={{ margin: '4px 0 0 0', color: address && factoryOwner && address.toLowerCase() === (factoryOwner as string).toLowerCase() ? '#2e7d32' : '#e65100' }}>
|
||||||
|
{address && factoryOwner && address.toLowerCase() === (factoryOwner as string).toLowerCase() ? 'You are Factory Owner' : 'You are not Factory Owner'}
|
||||||
|
</p>
|
||||||
|
</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"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 设置硬顶 */}
|
||||||
|
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.setHardCap')}</h4>
|
||||||
|
<div className="form-grid" style={{ marginBottom: '16px' }}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('vault.newHardCap')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={hardCapForm}
|
||||||
|
onChange={(e) => setHardCapForm(e.target.value)}
|
||||||
|
placeholder="100000"
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group" style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||||
|
<button onClick={handleSetHardCap} disabled={isPending || isConfirming || !hardCapForm} className="btn btn-secondary">
|
||||||
|
{isPending || isConfirming ? '...' : t('vault.setHardCap')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 设置赎回时间 */}
|
||||||
|
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.setRedemptionTime')}</h4>
|
||||||
|
<div className="form-grid" style={{ marginBottom: '16px' }}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('vault.newRedemptionTime')}</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
value={redemptionTimeForm}
|
||||||
|
onChange={(e) => setRedemptionTimeForm(e.target.value)}
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group" style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||||
|
<button onClick={handleSetRedemptionTime} disabled={isPending || isConfirming || !redemptionTimeForm} className="btn btn-secondary">
|
||||||
|
{isPending || isConfirming ? '...' : t('vault.setRedemptionTime')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 设置管理员 */}
|
||||||
|
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.setManager')}</h4>
|
||||||
|
<div className="form-grid">
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('vault.newManager')}</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={managerForm}
|
||||||
|
onChange={(e) => setManagerForm(e.target.value)}
|
||||||
|
placeholder="0x..."
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group" style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||||
|
<button onClick={handleSetManager} disabled={isPending || isConfirming || !managerForm} className="btn btn-secondary">
|
||||||
|
{isPending || isConfirming ? '...' : t('vault.setManager')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Manager 资产管理区域 */}
|
||||||
|
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||||
|
<div
|
||||||
|
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>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showManagerPanel ? '▼' : '▶'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showManagerPanel && (
|
||||||
|
<div style={{ marginTop: '10px', padding: '12px', background: '#fff', borderRadius: '6px' }}>
|
||||||
|
{/* 当前托管资产信息 */}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('vault.idleAssets')}:</span>
|
||||||
|
<strong>{vaultInfo ? formatUnits(vaultInfo[1], 18) : '0'} WUSD</strong>
|
||||||
|
</div>
|
||||||
|
<div className="info-row" style={{ fontSize: '11px', color: address && vaultManager && address.toLowerCase() === vaultManager.toLowerCase() ? '#2e7d32' : '#e65100' }}>
|
||||||
|
{address && vaultManager && address.toLowerCase() === vaultManager.toLowerCase()
|
||||||
|
? t('vault.youAreManager')
|
||||||
|
: t('vault.youAreNotManager')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 存入托管资产 */}
|
||||||
|
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.depositManagedAssets')}</h4>
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={depositManagedAmount}
|
||||||
|
onChange={(e) => setDepositManagedAmount(e.target.value)}
|
||||||
|
placeholder={t('common.amount')}
|
||||||
|
className="input"
|
||||||
|
style={{ marginBottom: '8px' }}
|
||||||
|
/>
|
||||||
|
{/* 授权状态提示 */}
|
||||||
|
<div style={{ fontSize: '11px', color: '#666', marginBottom: '8px' }}>
|
||||||
|
{t('vault.approvedAmount')}: {managerWusdAllowance !== undefined ? formatUnits(managerWusdAllowance, 18) : '...'} WUSD
|
||||||
|
{depositManagedAmount && ` | ${t('vault.needApprove')}: ${managerNeedsApproval ? t('test.yes') : t('test.no')}`}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
{managerNeedsApproval ? (
|
||||||
|
<button
|
||||||
|
onClick={handleManagerApproveWusd}
|
||||||
|
disabled={isPending || isConfirming || !depositManagedAmount}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? '...' : t('vault.approveWusd')}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleDepositManagedAssets}
|
||||||
|
disabled={isPending || isConfirming || !depositManagedAmount}
|
||||||
|
className="btn btn-primary"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? '...' : t('vault.deposit')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 提取托管资产 */}
|
||||||
|
<h4 style={{ fontSize: '14px', marginBottom: '12px' }}>{t('vault.withdrawForManagement')}</h4>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={withdrawManagedAmount}
|
||||||
|
onChange={(e) => setWithdrawManagedAmount(e.target.value)}
|
||||||
|
placeholder={t('common.amount')}
|
||||||
|
className="input"
|
||||||
|
style={{ marginBottom: '8px' }}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={withdrawToAddress}
|
||||||
|
onChange={(e) => setWithdrawToAddress(e.target.value)}
|
||||||
|
placeholder={t('vault.toAddress') + ' (' + t('vault.defaultSelf') + ')'}
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleWithdrawForManagement}
|
||||||
|
disabled={isPending || isConfirming || !withdrawManagedAmount}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? '...' : t('vault.withdraw')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 边界测试区域 */}
|
{/* 边界测试区域 */}
|
||||||
<div className="section">
|
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||||
<div className="section-header" onClick={() => setShowBoundaryTest(!showBoundaryTest)} style={{ cursor: 'pointer' }}>
|
<div
|
||||||
<h3>{t('test.boundaryTests')} {showBoundaryTest ? '[-]' : '[+]'}</h3>
|
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>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showBoundaryTest ? '▼' : '▶'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showBoundaryTest && (
|
{showBoundaryTest && (
|
||||||
<>
|
<div style={{ marginTop: '10px' }}>
|
||||||
<div className="test-hint">{t('test.boundaryHint')}</div>
|
<div className="test-hint">{t('test.boundaryHint')}</div>
|
||||||
|
|
||||||
{/* 赎回状态 */}
|
{/* 赎回状态 */}
|
||||||
@@ -692,76 +1065,75 @@ export function VaultPanel() {
|
|||||||
|
|
||||||
<div className="test-grid">
|
<div className="test-grid">
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.buyZero')}</span>
|
<span className="test-name">{t('test.buyZero')}</span>
|
||||||
<span className="test-error">InvalidAmount</span>
|
<span className="test-error">InvalidAmount</span>
|
||||||
|
<p className="test-desc">{t('test.buyZeroDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('buy_zero')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.buyZeroDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('buy_zero')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.sellZero')}</span>
|
<span className="test-name">{t('test.sellZero')}</span>
|
||||||
<span className="test-error">InvalidAmount</span>
|
<span className="test-error">InvalidAmount</span>
|
||||||
|
<p className="test-desc">{t('test.sellZeroDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('sell_zero')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.sellZeroDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('sell_zero')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.buyExceedBalance')}</span>
|
<span className="test-name">{t('test.buyExceedBalance')}</span>
|
||||||
<span className="test-error">InsufficientWUSD</span>
|
<span className="test-error">InsufficientWUSD</span>
|
||||||
|
<p className="test-desc">{t('test.buyExceedBalanceDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('buy_exceed_balance')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.buyExceedBalanceDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('buy_exceed_balance')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.sellExceedBalance')}</span>
|
<span className="test-name">{t('test.sellExceedBalance')}</span>
|
||||||
<span className="test-error">InsufficientYTA</span>
|
<span className="test-error">InsufficientYT</span>
|
||||||
|
<p className="test-desc">{t('test.sellExceedBalanceDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('sell_exceed_balance')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.sellExceedBalanceDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('sell_exceed_balance')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.buyExceedHardcap')}</span>
|
<span className="test-name">{t('test.buyExceedHardcap')}</span>
|
||||||
<span className="test-error">HardCapExceeded</span>
|
<span className="test-error">HardCapExceeded</span>
|
||||||
|
<p className="test-desc">{t('test.buyExceedHardcapDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('buy_exceed_hardcap')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.buyExceedHardcapDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('buy_exceed_hardcap')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.sellInLock')}</span>
|
<span className="test-name">{t('test.sellInLock')}</span>
|
||||||
<span className="test-error">StillInLockPeriod</span>
|
<span className="test-error">StillInLockPeriod</span>
|
||||||
|
<p className="test-desc">{t('test.sellInLockDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('sell_in_lock')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="test-card">
|
||||||
|
<div className="test-card-left">
|
||||||
|
<span className="test-name">{t('test.maxApprove')}</span>
|
||||||
|
<span className="test-error">MaxUint256</span>
|
||||||
|
<p className="test-desc">{t('test.maxApproveDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('max_approve')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.sellInLockDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('sell_in_lock')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="quick-actions" style={{ marginTop: '16px' }}>
|
|
||||||
<button onClick={() => runBoundaryTest('max_approve')} disabled={isProcessing} className="btn btn-secondary">
|
|
||||||
{t('test.maxApprove')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{testResult && (
|
{testResult && (
|
||||||
@@ -769,7 +1141,7 @@ export function VaultPanel() {
|
|||||||
{testResult.msg}
|
{testResult.msg}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export function WUSDPanel() {
|
|||||||
const { transactions, addTransaction, updateTransaction, clearHistory } = useTransactions()
|
const { transactions, addTransaction, updateTransaction, clearHistory } = useTransactions()
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const [mintAmount, setMintAmount] = useState('')
|
const [mintAmount, setMintAmount] = useState('')
|
||||||
|
const [burnAmount, setBurnAmount] = useState('')
|
||||||
const [showBoundaryTest, setShowBoundaryTest] = useState(false)
|
const [showBoundaryTest, setShowBoundaryTest] = useState(false)
|
||||||
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
||||||
|
|
||||||
@@ -161,7 +162,21 @@ export function WUSDPanel() {
|
|||||||
abi: WUSD_ABI,
|
abi: WUSD_ABI,
|
||||||
functionName: 'mint',
|
functionName: 'mint',
|
||||||
args: [address, parseUnits(mintAmount, decimals)],
|
args: [address, parseUnits(mintAmount, decimals)],
|
||||||
gas: GAS_CONFIG.SIMPLE,
|
gas: GAS_CONFIG.STANDARD,
|
||||||
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBurn = async () => {
|
||||||
|
if (!address || !burnAmount || !decimals) return
|
||||||
|
recordTx('burn', burnAmount, 'WUSD')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.WUSD,
|
||||||
|
abi: WUSD_ABI,
|
||||||
|
functionName: 'burn',
|
||||||
|
args: [address, parseUnits(burnAmount, decimals)],
|
||||||
|
gas: GAS_CONFIG.STANDARD,
|
||||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
})
|
})
|
||||||
@@ -178,7 +193,7 @@ export function WUSDPanel() {
|
|||||||
abi: WUSD_ABI,
|
abi: WUSD_ABI,
|
||||||
functionName: 'mint',
|
functionName: 'mint',
|
||||||
args: [address, BigInt(0)],
|
args: [address, BigInt(0)],
|
||||||
gas: GAS_CONFIG.SIMPLE,
|
gas: GAS_CONFIG.STANDARD,
|
||||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
})
|
})
|
||||||
@@ -190,7 +205,7 @@ export function WUSDPanel() {
|
|||||||
abi: WUSD_ABI,
|
abi: WUSD_ABI,
|
||||||
functionName: 'burn',
|
functionName: 'burn',
|
||||||
args: [address, exceedAmount],
|
args: [address, exceedAmount],
|
||||||
gas: GAS_CONFIG.SIMPLE,
|
gas: GAS_CONFIG.STANDARD,
|
||||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
})
|
})
|
||||||
@@ -202,7 +217,7 @@ export function WUSDPanel() {
|
|||||||
abi: WUSD_ABI,
|
abi: WUSD_ABI,
|
||||||
functionName: 'mint',
|
functionName: 'mint',
|
||||||
args: [address, parseUnits('10000', decimals)],
|
args: [address, parseUnits('10000', decimals)],
|
||||||
gas: GAS_CONFIG.SIMPLE,
|
gas: GAS_CONFIG.STANDARD,
|
||||||
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
maxFeePerGas: GAS_CONFIG.MAX_FEE_PER_GAS,
|
||||||
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
maxPriorityFeePerGas: GAS_CONFIG.MAX_PRIORITY_FEE_PER_GAS,
|
||||||
})
|
})
|
||||||
@@ -235,65 +250,101 @@ export function WUSDPanel() {
|
|||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
|
||||||
<label>{t('wusd.mintAmount')}</label>
|
{/* 铸造 */}
|
||||||
<input
|
<div>
|
||||||
type="number"
|
<div className="form-group">
|
||||||
value={mintAmount}
|
<label>{t('wusd.mintAmount')}</label>
|
||||||
onChange={(e) => setMintAmount(e.target.value)}
|
<input
|
||||||
placeholder={t('wusd.enterAmount')}
|
type="number"
|
||||||
className="input"
|
value={mintAmount}
|
||||||
/>
|
onChange={(e) => setMintAmount(e.target.value)}
|
||||||
|
placeholder={t('wusd.enterAmount')}
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleMint}
|
||||||
|
disabled={isProcessing || !mintAmount}
|
||||||
|
className="btn btn-primary"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{isProcessing ? t('common.processing') : t('wusd.mint')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 销毁 */}
|
||||||
|
<div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('wusd.burnAmount')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={burnAmount}
|
||||||
|
onChange={(e) => setBurnAmount(e.target.value)}
|
||||||
|
placeholder={t('wusd.enterAmount')}
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleBurn}
|
||||||
|
disabled={isProcessing || !burnAmount}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{isProcessing ? t('common.processing') : t('wusd.burn')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleMint}
|
|
||||||
disabled={isProcessing || !mintAmount}
|
|
||||||
className="btn btn-primary"
|
|
||||||
>
|
|
||||||
{isProcessing ? (isPending ? t('wusd.confirming') : t('wusd.minting')) : t('wusd.mint')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* 边界测试区域 */}
|
{/* 边界测试区域 */}
|
||||||
<div className="section">
|
<div style={{ marginTop: '12px', padding: '8px 12px', background: '#f5f5f5', borderRadius: '8px' }}>
|
||||||
<div className="section-header" onClick={() => setShowBoundaryTest(!showBoundaryTest)} style={{ cursor: 'pointer' }}>
|
<div
|
||||||
<h3>{t('test.boundaryTests')} {showBoundaryTest ? '[-]' : '[+]'}</h3>
|
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>
|
||||||
|
<span style={{ color: '#999', fontSize: '16px' }}>{showBoundaryTest ? '▼' : '▶'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showBoundaryTest && (
|
{showBoundaryTest && (
|
||||||
<>
|
<div style={{ marginTop: '10px' }}>
|
||||||
<div className="test-hint">{t('test.boundaryHint')}</div>
|
<div className="test-hint">{t('test.boundaryHint')}</div>
|
||||||
|
|
||||||
<div className="test-grid">
|
<div className="test-grid">
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.mintZero')}</span>
|
<span className="test-name">{t('test.mintZero')}</span>
|
||||||
<span className="test-error">MaySucceed</span>
|
<span className="test-error">MaySucceed</span>
|
||||||
|
<p className="test-desc">{t('test.mintZeroDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('mint_zero')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.mintZeroDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('mint_zero')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="test-card">
|
<div className="test-card">
|
||||||
<div className="test-header">
|
<div className="test-card-left">
|
||||||
<span className="test-name">{t('test.burnExceed')}</span>
|
<span className="test-name">{t('test.burnExceed')}</span>
|
||||||
<span className="test-error">ERC20InsufficientBalance</span>
|
<span className="test-error">InsufficientBalance</span>
|
||||||
|
<p className="test-desc">{t('test.burnExceedDesc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('burn_exceed')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="test-card">
|
||||||
|
<div className="test-card-left">
|
||||||
|
<span className="test-name">{t('test.mint10000')}</span>
|
||||||
|
<span className="test-error">Mint</span>
|
||||||
|
<p className="test-desc">{t('test.mint10000Desc')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="test-card-right">
|
||||||
|
<button onClick={() => runBoundaryTest('mint_10000')} disabled={isProcessing} className="btn btn-secondary btn-sm">{t('test.run')}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="test-desc">{t('test.burnExceedDesc')}</p>
|
|
||||||
<button onClick={() => runBoundaryTest('burn_exceed')} disabled={isProcessing} className="btn btn-warning btn-sm">
|
|
||||||
{t('test.run')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="quick-actions" style={{ marginTop: '16px' }}>
|
|
||||||
<button onClick={() => runBoundaryTest('mint_10000')} disabled={isProcessing} className="btn btn-primary">
|
|
||||||
{t('test.mint10000')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const CONTRACTS = {
|
|||||||
YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as const,
|
YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as const,
|
||||||
YT_B: '0x333805C9EE75f59Aa2Cc79DfDe2499F920c7b408' as const,
|
YT_B: '0x333805C9EE75f59Aa2Cc79DfDe2499F920c7b408' as const,
|
||||||
YT_C: '0x6DF0ED6f0345F601A206974973dE9fC970598587' as const,
|
YT_C: '0x6DF0ED6f0345F601A206974973dE9fC970598587' as const,
|
||||||
|
YT_D: '0x5d91FD16fa85547b0784c377A47BF7706D7875d3' as const,
|
||||||
},
|
},
|
||||||
// LP Pool contracts
|
// LP Pool contracts
|
||||||
YT_REWARD_ROUTER: '0x51eEF57eC57c867AC23945f0ce21aA5A9a2C246c' as const,
|
YT_REWARD_ROUTER: '0x51eEF57eC57c867AC23945f0ce21aA5A9a2C246c' as const,
|
||||||
@@ -189,6 +190,81 @@ export const FACTORY_ABI = [
|
|||||||
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
stateMutability: 'view',
|
stateMutability: 'view',
|
||||||
type: 'function'
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 批量获取金库(分页)
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'uint256', name: '_start', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_end', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'getVaults',
|
||||||
|
outputs: [{ internalType: 'address[]', name: 'vaults', type: 'address[]' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 批量设置硬顶
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address[]', name: '_vaults', type: 'address[]' },
|
||||||
|
{ internalType: 'uint256[]', name: '_hardCaps', type: 'uint256[]' }
|
||||||
|
],
|
||||||
|
name: 'setHardCapBatch',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 批量更新价格
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address[]', name: '_vaults', type: 'address[]' },
|
||||||
|
{ internalType: 'uint256[]', name: '_wusdPrices', type: 'uint256[]' },
|
||||||
|
{ internalType: 'uint256[]', name: '_ytPrices', type: 'uint256[]' }
|
||||||
|
],
|
||||||
|
name: 'updateVaultPricesBatch',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 批量设置赎回时间
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address[]', name: '_vaults', type: 'address[]' },
|
||||||
|
{ internalType: 'uint256', name: '_nextRedemptionTime', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'setVaultNextRedemptionTimeBatch',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 设置金库实现地址
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_newImplementation', type: 'address' }],
|
||||||
|
name: 'setVaultImplementation',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 升级单个金库
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_vault', type: 'address' },
|
||||||
|
{ internalType: 'address', name: '_newImplementation', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'upgradeVault',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 批量升级金库
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address[]', name: '_vaults', type: 'address[]' },
|
||||||
|
{ internalType: 'address', name: '_newImplementation', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'upgradeVaultBatch',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
}
|
}
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
@@ -974,5 +1050,120 @@ export const YT_VAULT_ABI = [
|
|||||||
outputs: [],
|
outputs: [],
|
||||||
stateMutability: 'nonpayable',
|
stateMutability: 'nonpayable',
|
||||||
type: 'function'
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 获取交换手续费率 - 用于滑点计算
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_tokenIn', type: 'address' },
|
||||||
|
{ internalType: 'address', name: '_tokenOut', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: '_usdyAmount', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'getSwapFeeBasisPoints',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 获取赎回手续费率 - 用于移除流动性滑点计算
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_token', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: '_usdyAmount', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'getRedemptionFeeBasisPoints',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 获取动态手续费率
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_token', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: '_usdyDelta', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_feeBasisPoints', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_taxBasisPoints', type: 'uint256' },
|
||||||
|
{ internalType: 'bool', name: '_increment', type: 'bool' }
|
||||||
|
],
|
||||||
|
name: 'getFeeBasisPoints',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 获取池子总价值
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'bool', name: '_maximise', type: 'bool' }],
|
||||||
|
name: 'getPoolValue',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 获取所有池子代币
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'getAllPoolTokens',
|
||||||
|
outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 常量
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'BASIS_POINTS_DIVISOR',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'PRICE_PRECISION',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'USDY_DECIMALS',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 紧急模式
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'emergencyMode',
|
||||||
|
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// swap 开关
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'swapEnabled',
|
||||||
|
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 设置紧急模式
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'bool', name: '_emergencyMode', type: 'bool' }],
|
||||||
|
name: 'setEmergencyMode',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 设置 swap 开关
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'bool', name: '_isSwapEnabled', type: 'bool' }],
|
||||||
|
name: 'setSwapEnabled',
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
// 稳定币标记
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
name: 'stableTokens',
|
||||||
|
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
}
|
}
|
||||||
] as const
|
] as const
|
||||||
|
|||||||
@@ -33,8 +33,10 @@
|
|||||||
"wusd": {
|
"wusd": {
|
||||||
"title": "WUSD Token",
|
"title": "WUSD Token",
|
||||||
"mintAmount": "Mint Amount",
|
"mintAmount": "Mint Amount",
|
||||||
"enterAmount": "Enter amount to mint",
|
"burnAmount": "Burn Amount",
|
||||||
"mint": "Mint WUSD",
|
"enterAmount": "Enter amount",
|
||||||
|
"mint": "Mint",
|
||||||
|
"burn": "Burn",
|
||||||
"minting": "Minting...",
|
"minting": "Minting...",
|
||||||
"confirming": "Confirming...",
|
"confirming": "Confirming...",
|
||||||
"mintSuccess": "Mint successful!"
|
"mintSuccess": "Mint successful!"
|
||||||
@@ -65,12 +67,33 @@
|
|||||||
"redeemAvailable": "Available",
|
"redeemAvailable": "Available",
|
||||||
"redeemNotAvailable": "Not Available",
|
"redeemNotAvailable": "Not Available",
|
||||||
"redeemLocked": "Redemption Locked",
|
"redeemLocked": "Redemption Locked",
|
||||||
"timeRemaining": "Time Remaining"
|
"timeRemaining": "Time Remaining",
|
||||||
|
"adminConfig": "Admin Config",
|
||||||
|
"updatePrices": "Update Prices",
|
||||||
|
"setHardCap": "Set Hard Cap",
|
||||||
|
"setRedemptionTime": "Set Redemption Time",
|
||||||
|
"setManager": "Set Manager",
|
||||||
|
"newHardCap": "New Hard Cap",
|
||||||
|
"newManager": "New Manager Address",
|
||||||
|
"newRedemptionTime": "New Redemption Time",
|
||||||
|
"managerPanel": "Manager Panel",
|
||||||
|
"managedAssets": "Managed Assets",
|
||||||
|
"youAreManager": "You are the Manager of this vault",
|
||||||
|
"youAreNotManager": "You are not the Manager of this vault",
|
||||||
|
"depositManagedAssets": "Deposit Managed Assets",
|
||||||
|
"withdrawForManagement": "Withdraw For Management",
|
||||||
|
"deposit": "Deposit",
|
||||||
|
"withdraw": "Withdraw",
|
||||||
|
"toAddress": "To Address",
|
||||||
|
"defaultSelf": "default: self",
|
||||||
|
"approvedAmount": "Approved Amount",
|
||||||
|
"needApprove": "Need Approve"
|
||||||
},
|
},
|
||||||
"factory": {
|
"factory": {
|
||||||
"title": "Factory Management",
|
"title": "Factory Management",
|
||||||
"factoryContract": "Factory Contract",
|
"factoryContract": "Factory Contract",
|
||||||
"owner": "Owner",
|
"owner": "Owner",
|
||||||
|
"vaultImpl": "Vault Implementation",
|
||||||
"defaultHardCap": "Default Hard Cap",
|
"defaultHardCap": "Default Hard Cap",
|
||||||
"totalVaults": "Total Vaults",
|
"totalVaults": "Total Vaults",
|
||||||
"yourRole": "Your Role",
|
"yourRole": "Your Role",
|
||||||
@@ -91,7 +114,27 @@
|
|||||||
"selectVault": "Select Vault",
|
"selectVault": "Select Vault",
|
||||||
"newWusdPrice": "New WUSD Price",
|
"newWusdPrice": "New WUSD Price",
|
||||||
"newYtPrice": "New YT Price",
|
"newYtPrice": "New YT Price",
|
||||||
"update": "Update Prices"
|
"update": "Update Prices",
|
||||||
|
"ownerConfig": "Owner Config",
|
||||||
|
"setDefaultHardCap": "Set Default Hard Cap",
|
||||||
|
"batchOperations": "Batch Operations",
|
||||||
|
"selectVaultsForBatch": "Select Vaults",
|
||||||
|
"selectAll": "Select All",
|
||||||
|
"deselectAll": "Deselect All",
|
||||||
|
"selectedCount": "Selected",
|
||||||
|
"batchUpdatePrices": "Batch Update Prices",
|
||||||
|
"batchSetHardCap": "Batch Set Hard Cap",
|
||||||
|
"batchSetRedemptionTime": "Batch Set Redemption Time",
|
||||||
|
"advancedFunctions": "Advanced Functions",
|
||||||
|
"advancedWarning": "Warning: These are advanced management functions. Please ensure you understand the implications before proceeding. Incorrect operations may cause contract issues.",
|
||||||
|
"setVaultImplementation": "Set Vault Implementation",
|
||||||
|
"currentImpl": "Current Implementation",
|
||||||
|
"upgradeVault": "Upgrade Vault",
|
||||||
|
"newImplAddress": "New Implementation Address",
|
||||||
|
"upgrade": "Upgrade",
|
||||||
|
"batchUpgradeVault": "Batch Upgrade Vaults",
|
||||||
|
"batchUpgrade": "Batch Upgrade",
|
||||||
|
"selectInBatchSection": "select in batch section"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"en": "English",
|
"en": "English",
|
||||||
@@ -136,7 +179,9 @@
|
|||||||
"run": "Run",
|
"run": "Run",
|
||||||
"running": "Running...",
|
"running": "Running...",
|
||||||
"mint10000": "Mint 10000 WUSD",
|
"mint10000": "Mint 10000 WUSD",
|
||||||
|
"mint10000Desc": "Quick mint 10000 test WUSD",
|
||||||
"maxApprove": "Max Approve WUSD",
|
"maxApprove": "Max Approve WUSD",
|
||||||
|
"maxApproveDesc": "Approve max uint256 amount",
|
||||||
"buyZero": "Buy Amount 0",
|
"buyZero": "Buy Amount 0",
|
||||||
"buyZeroDesc": "Test depositYT(0)",
|
"buyZeroDesc": "Test depositYT(0)",
|
||||||
"sellZero": "Sell Amount 0",
|
"sellZero": "Sell Amount 0",
|
||||||
@@ -207,6 +252,18 @@
|
|||||||
"testSwapExceedDesc": "Swap amount exceeding token balance",
|
"testSwapExceedDesc": "Swap amount exceeding token balance",
|
||||||
"yourTokenBalances": "Your Token Balances",
|
"yourTokenBalances": "Your Token Balances",
|
||||||
"whitelisted": "Whitelisted",
|
"whitelisted": "Whitelisted",
|
||||||
"notWhitelisted": "Not Whitelisted"
|
"notWhitelisted": "Not Whitelisted",
|
||||||
|
"emergencyMode": "Emergency Mode",
|
||||||
|
"emergencyModeOn": "ON (Warning)",
|
||||||
|
"emergencyModeOff": "Normal",
|
||||||
|
"swapEnabled": "Swap Function",
|
||||||
|
"swapEnabledOn": "Enabled",
|
||||||
|
"swapEnabledOff": "Disabled",
|
||||||
|
"totalTokenWeights": "Total Weights",
|
||||||
|
"accountValue": "Your Account Value",
|
||||||
|
"usdyAmount": "USDY Amount",
|
||||||
|
"isStableToken": "Stable Token",
|
||||||
|
"setStableToken": "Set Stable Token",
|
||||||
|
"stableTokenHint": "Mark token as stable, affects price calculation"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,10 @@
|
|||||||
"wusd": {
|
"wusd": {
|
||||||
"title": "WUSD 代币",
|
"title": "WUSD 代币",
|
||||||
"mintAmount": "铸造数量",
|
"mintAmount": "铸造数量",
|
||||||
"enterAmount": "输入铸造数量",
|
"burnAmount": "销毁数量",
|
||||||
"mint": "铸造 WUSD",
|
"enterAmount": "输入数量",
|
||||||
|
"mint": "铸造",
|
||||||
|
"burn": "销毁",
|
||||||
"minting": "铸造中...",
|
"minting": "铸造中...",
|
||||||
"confirming": "确认中...",
|
"confirming": "确认中...",
|
||||||
"mintSuccess": "铸造成功!"
|
"mintSuccess": "铸造成功!"
|
||||||
@@ -65,12 +67,33 @@
|
|||||||
"redeemAvailable": "可以赎回",
|
"redeemAvailable": "可以赎回",
|
||||||
"redeemNotAvailable": "暂不可赎回",
|
"redeemNotAvailable": "暂不可赎回",
|
||||||
"redeemLocked": "赎回锁定中",
|
"redeemLocked": "赎回锁定中",
|
||||||
"timeRemaining": "剩余时间"
|
"timeRemaining": "剩余时间",
|
||||||
|
"adminConfig": "管理员配置",
|
||||||
|
"updatePrices": "更新价格",
|
||||||
|
"setHardCap": "设置硬顶",
|
||||||
|
"setRedemptionTime": "设置赎回时间",
|
||||||
|
"setManager": "设置管理员",
|
||||||
|
"newHardCap": "新硬顶",
|
||||||
|
"newManager": "新管理员地址",
|
||||||
|
"newRedemptionTime": "新赎回时间",
|
||||||
|
"managerPanel": "Manager 面板",
|
||||||
|
"managedAssets": "托管资产",
|
||||||
|
"youAreManager": "你是当前金库的 Manager",
|
||||||
|
"youAreNotManager": "你不是当前金库的 Manager",
|
||||||
|
"depositManagedAssets": "存入托管资产",
|
||||||
|
"withdrawForManagement": "提取托管资产",
|
||||||
|
"deposit": "存入",
|
||||||
|
"withdraw": "提取",
|
||||||
|
"toAddress": "接收地址",
|
||||||
|
"defaultSelf": "默认为自己",
|
||||||
|
"approvedAmount": "已授权额度",
|
||||||
|
"needApprove": "需要授权"
|
||||||
},
|
},
|
||||||
"factory": {
|
"factory": {
|
||||||
"title": "工厂管理",
|
"title": "工厂管理",
|
||||||
"factoryContract": "工厂合约",
|
"factoryContract": "工厂合约",
|
||||||
"owner": "所有者",
|
"owner": "所有者",
|
||||||
|
"vaultImpl": "金库实现",
|
||||||
"defaultHardCap": "默认硬顶",
|
"defaultHardCap": "默认硬顶",
|
||||||
"totalVaults": "金库总数",
|
"totalVaults": "金库总数",
|
||||||
"yourRole": "你的角色",
|
"yourRole": "你的角色",
|
||||||
@@ -91,7 +114,27 @@
|
|||||||
"selectVault": "选择金库",
|
"selectVault": "选择金库",
|
||||||
"newWusdPrice": "新 WUSD 价格",
|
"newWusdPrice": "新 WUSD 价格",
|
||||||
"newYtPrice": "新 YT 价格",
|
"newYtPrice": "新 YT 价格",
|
||||||
"update": "更新价格"
|
"update": "更新价格",
|
||||||
|
"ownerConfig": "Owner 配置",
|
||||||
|
"setDefaultHardCap": "设置默认硬顶",
|
||||||
|
"batchOperations": "批量操作",
|
||||||
|
"selectVaultsForBatch": "选择金库",
|
||||||
|
"selectAll": "全选",
|
||||||
|
"deselectAll": "取消全选",
|
||||||
|
"selectedCount": "已选择",
|
||||||
|
"batchUpdatePrices": "批量更新价格",
|
||||||
|
"batchSetHardCap": "批量设置硬顶",
|
||||||
|
"batchSetRedemptionTime": "批量设置赎回时间",
|
||||||
|
"advancedFunctions": "高级功能",
|
||||||
|
"advancedWarning": "警告: 以下是高级管理功能,操作前请确保了解其影响。错误的操作可能导致合约异常。",
|
||||||
|
"setVaultImplementation": "设置金库实现",
|
||||||
|
"currentImpl": "当前实现",
|
||||||
|
"upgradeVault": "升级金库",
|
||||||
|
"newImplAddress": "新实现地址",
|
||||||
|
"upgrade": "升级",
|
||||||
|
"batchUpgradeVault": "批量升级金库",
|
||||||
|
"batchUpgrade": "批量升级",
|
||||||
|
"selectInBatchSection": "在批量操作区选择"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"en": "英文",
|
"en": "英文",
|
||||||
@@ -136,7 +179,9 @@
|
|||||||
"run": "执行",
|
"run": "执行",
|
||||||
"running": "执行中...",
|
"running": "执行中...",
|
||||||
"mint10000": "铸造 10000 WUSD",
|
"mint10000": "铸造 10000 WUSD",
|
||||||
|
"mint10000Desc": "快速铸造10000个测试WUSD",
|
||||||
"maxApprove": "最大授权 WUSD",
|
"maxApprove": "最大授权 WUSD",
|
||||||
|
"maxApproveDesc": "授权最大uint256额度",
|
||||||
"buyZero": "买入金额为0",
|
"buyZero": "买入金额为0",
|
||||||
"buyZeroDesc": "测试 depositYT(0)",
|
"buyZeroDesc": "测试 depositYT(0)",
|
||||||
"sellZero": "卖出金额为0",
|
"sellZero": "卖出金额为0",
|
||||||
@@ -207,6 +252,18 @@
|
|||||||
"testSwapExceedDesc": "互换金额超过代币余额",
|
"testSwapExceedDesc": "互换金额超过代币余额",
|
||||||
"yourTokenBalances": "你的代币余额",
|
"yourTokenBalances": "你的代币余额",
|
||||||
"whitelisted": "已白名单",
|
"whitelisted": "已白名单",
|
||||||
"notWhitelisted": "未白名单"
|
"notWhitelisted": "未白名单",
|
||||||
|
"emergencyMode": "紧急模式",
|
||||||
|
"emergencyModeOn": "已开启 (警告)",
|
||||||
|
"emergencyModeOff": "正常",
|
||||||
|
"swapEnabled": "交换功能",
|
||||||
|
"swapEnabledOn": "已开启",
|
||||||
|
"swapEnabledOff": "已关闭",
|
||||||
|
"totalTokenWeights": "总权重",
|
||||||
|
"accountValue": "你的账户价值",
|
||||||
|
"usdyAmount": "USDY 数量",
|
||||||
|
"isStableToken": "稳定币",
|
||||||
|
"setStableToken": "设置稳定币",
|
||||||
|
"stableTokenHint": "将代币标记为稳定币,影响价格计算方式"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user