feat: 添加 LP 流动性池功能
新增功能: - 添加 LPPanel 组件,支持流动性池操作 - 添加流动性 (addLiquidity): 存入 YT 代币或 WUSD 获得 ytLP - 移除流动性 (removeLiquidity): 销毁 ytLP 获取代币 - 代币互换 (swapYT): 在池内交换 YT 代币 合约集成: - YTRewardRouter: 0x51eEF57eC57c867AC23945f0ce21aA5A9a2C246c - YTLPToken: 0x1b96F219E8aeE557DD8bD905a6c72cc64eA5BD7B - YTPoolManager: 0x14246886a1E1202cb6b5a2db793eF3359d536302 - YTVault: 0x19982e5145ca5401A1084c0BF916c0E0bB343Af9 - USDY: 0x631Bd6834C50f6d2B07035c9253b4a19132E888c UI功能: - 显示池子 AUM、ytLP 价格、总供应量 - 显示用户 ytLP 余额和冷却时间 - Tab 切换: 添加流动性/移除流动性/代币互换 - 代币授权检查和一键授权 - 滑点容忍度设置 - 中英文翻译支持 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -824,3 +824,66 @@ body {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LP Panel Styles */
|
||||||
|
.pool-info {
|
||||||
|
background: #f0f7ff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lp-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn:hover {
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn.active {
|
||||||
|
color: #1976d2;
|
||||||
|
border-bottom-color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box {
|
||||||
|
background: #fff3e0;
|
||||||
|
color: #e65100;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-left: 4px solid #ff9800;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #1976d2;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-link:hover {
|
||||||
|
color: #1565c0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import { LanguageSwitch } from './components/LanguageSwitch'
|
|||||||
import { WUSDPanel } from './components/WUSDPanel'
|
import { WUSDPanel } from './components/WUSDPanel'
|
||||||
import { VaultPanel } from './components/VaultPanel'
|
import { VaultPanel } from './components/VaultPanel'
|
||||||
import { FactoryPanel } from './components/FactoryPanel'
|
import { FactoryPanel } from './components/FactoryPanel'
|
||||||
|
import { LPPanel } from './components/LPPanel'
|
||||||
import { ToastProvider } from './components/Toast'
|
import { ToastProvider } from './components/Toast'
|
||||||
import { TransactionProvider } from './context/TransactionContext'
|
import { TransactionProvider } from './context/TransactionContext'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
|
||||||
type Tab = 'wusd' | 'vault' | 'factory'
|
type Tab = 'wusd' | 'vault' | 'factory' | 'lp'
|
||||||
|
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -48,12 +49,19 @@ function AppContent() {
|
|||||||
>
|
>
|
||||||
{t('nav.factory')}
|
{t('nav.factory')}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={`nav-btn ${activeTab === 'lp' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('lp')}
|
||||||
|
>
|
||||||
|
{t('nav.lpPool')}
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main className="main">
|
<main className="main">
|
||||||
{activeTab === 'wusd' && <WUSDPanel />}
|
{activeTab === 'wusd' && <WUSDPanel />}
|
||||||
{activeTab === 'vault' && <VaultPanel />}
|
{activeTab === 'vault' && <VaultPanel />}
|
||||||
{activeTab === 'factory' && <FactoryPanel />}
|
{activeTab === 'factory' && <FactoryPanel />}
|
||||||
|
{activeTab === 'lp' && <LPPanel />}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
|
|||||||
622
frontend/src/components/LPPanel.tsx
Normal file
622
frontend/src/components/LPPanel.tsx
Normal file
@@ -0,0 +1,622 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
|
||||||
|
import { parseUnits, formatUnits } from 'viem'
|
||||||
|
import {
|
||||||
|
CONTRACTS,
|
||||||
|
YT_REWARD_ROUTER_ABI,
|
||||||
|
YT_LP_TOKEN_ABI,
|
||||||
|
YT_POOL_MANAGER_ABI,
|
||||||
|
WUSD_ABI
|
||||||
|
} from '../config/contracts'
|
||||||
|
import { useTransactions } from '../context/TransactionContext'
|
||||||
|
import type { TransactionType } from '../context/TransactionContext'
|
||||||
|
import { useToast } from './Toast'
|
||||||
|
import { TransactionHistory } from './TransactionHistory'
|
||||||
|
|
||||||
|
// Token list for the LP pool
|
||||||
|
const POOL_TOKENS = [
|
||||||
|
{ address: CONTRACTS.VAULTS.YT_A, symbol: 'YT-A', name: 'YT Token A' },
|
||||||
|
{ address: CONTRACTS.VAULTS.YT_B, symbol: 'YT-B', name: 'YT Token B' },
|
||||||
|
{ address: CONTRACTS.VAULTS.YT_C, symbol: 'YT-C', name: 'YT Token C' },
|
||||||
|
{ address: CONTRACTS.WUSD, symbol: 'WUSD', name: 'Wrapped USD' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function LPPanel() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { address, isConnected } = useAccount()
|
||||||
|
const { transactions, addTransaction, updateTransaction, clearHistory } = useTransactions()
|
||||||
|
const { showToast } = useToast()
|
||||||
|
const pendingTxRef = useRef<{ id: string; type: TransactionType; amount?: string } | null>(null)
|
||||||
|
|
||||||
|
// Form states
|
||||||
|
const [addLiquidityForm, setAddLiquidityForm] = useState<{
|
||||||
|
token: string
|
||||||
|
amount: string
|
||||||
|
slippage: string
|
||||||
|
}>({
|
||||||
|
token: CONTRACTS.VAULTS.YT_A,
|
||||||
|
amount: '',
|
||||||
|
slippage: '0.5',
|
||||||
|
})
|
||||||
|
const [removeLiquidityForm, setRemoveLiquidityForm] = useState<{
|
||||||
|
token: string
|
||||||
|
amount: string
|
||||||
|
slippage: string
|
||||||
|
}>({
|
||||||
|
token: CONTRACTS.VAULTS.YT_A,
|
||||||
|
amount: '',
|
||||||
|
slippage: '1',
|
||||||
|
})
|
||||||
|
const [swapForm, setSwapForm] = useState<{
|
||||||
|
tokenIn: string
|
||||||
|
tokenOut: string
|
||||||
|
amount: string
|
||||||
|
slippage: string
|
||||||
|
}>({
|
||||||
|
tokenIn: CONTRACTS.VAULTS.YT_A,
|
||||||
|
tokenOut: CONTRACTS.VAULTS.YT_B,
|
||||||
|
amount: '',
|
||||||
|
slippage: '0.5',
|
||||||
|
})
|
||||||
|
const [activeTab, setActiveTab] = useState<'add' | 'remove' | 'swap'>('add')
|
||||||
|
|
||||||
|
// Read pool data
|
||||||
|
const { data: ytLPBalance, refetch: refetchBalance } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_LP_TOKEN,
|
||||||
|
abi: YT_LP_TOKEN_ABI,
|
||||||
|
functionName: 'balanceOf',
|
||||||
|
args: address ? [address] : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: ytLPTotalSupply } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_LP_TOKEN,
|
||||||
|
abi: YT_LP_TOKEN_ABI,
|
||||||
|
functionName: 'totalSupply',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: ytLPPrice } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_REWARD_ROUTER,
|
||||||
|
abi: YT_REWARD_ROUTER_ABI,
|
||||||
|
functionName: 'getYtLPPrice',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: aumInUsdy } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_POOL_MANAGER,
|
||||||
|
abi: YT_POOL_MANAGER_ABI,
|
||||||
|
functionName: 'getAumInUsdy',
|
||||||
|
args: [true],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gov not currently used but may be needed later
|
||||||
|
// const { data: gov } = useReadContract({
|
||||||
|
// address: CONTRACTS.YT_REWARD_ROUTER,
|
||||||
|
// abi: YT_REWARD_ROUTER_ABI,
|
||||||
|
// functionName: 'gov',
|
||||||
|
// })
|
||||||
|
|
||||||
|
const { data: cooldownDuration } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_POOL_MANAGER,
|
||||||
|
abi: YT_POOL_MANAGER_ABI,
|
||||||
|
functionName: 'cooldownDuration',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: lastAddedAt } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_POOL_MANAGER,
|
||||||
|
abi: YT_POOL_MANAGER_ABI,
|
||||||
|
functionName: 'lastAddedAt',
|
||||||
|
args: address ? [address] : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check token allowance for the selected token
|
||||||
|
const { data: tokenAllowance, refetch: refetchAllowance } = useReadContract({
|
||||||
|
address: addLiquidityForm.token as `0x${string}`,
|
||||||
|
abi: WUSD_ABI,
|
||||||
|
functionName: 'allowance',
|
||||||
|
args: address ? [address, CONTRACTS.YT_REWARD_ROUTER] : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check ytLP allowance for remove liquidity
|
||||||
|
const { data: ytLPAllowance, refetch: refetchYtLPAllowance } = useReadContract({
|
||||||
|
address: CONTRACTS.YT_LP_TOKEN,
|
||||||
|
abi: YT_LP_TOKEN_ABI,
|
||||||
|
functionName: 'allowance',
|
||||||
|
args: address ? [address, CONTRACTS.YT_REWARD_ROUTER] : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { writeContract, data: hash, isPending, error: writeError, reset } = useWriteContract()
|
||||||
|
|
||||||
|
const { isLoading: isConfirming, isSuccess, isError, error: txError } = useWaitForTransactionReceipt({
|
||||||
|
hash,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle transaction submission
|
||||||
|
useEffect(() => {
|
||||||
|
if (hash && pendingTxRef.current) {
|
||||||
|
updateTransaction(pendingTxRef.current.id, { hash, status: 'pending' })
|
||||||
|
showToast('info', t('toast.txSubmitted'))
|
||||||
|
}
|
||||||
|
}, [hash])
|
||||||
|
|
||||||
|
// Handle transaction success
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess) {
|
||||||
|
if (pendingTxRef.current) {
|
||||||
|
updateTransaction(pendingTxRef.current.id, { status: 'success' })
|
||||||
|
showToast('success', t('toast.txSuccess'))
|
||||||
|
pendingTxRef.current = null
|
||||||
|
}
|
||||||
|
refetchBalance()
|
||||||
|
refetchAllowance()
|
||||||
|
refetchYtLPAllowance()
|
||||||
|
reset()
|
||||||
|
setAddLiquidityForm(f => ({ ...f, amount: '' }))
|
||||||
|
setRemoveLiquidityForm(f => ({ ...f, amount: '' }))
|
||||||
|
setSwapForm(f => ({ ...f, amount: '' }))
|
||||||
|
}
|
||||||
|
}, [isSuccess])
|
||||||
|
|
||||||
|
// Handle transaction error
|
||||||
|
useEffect(() => {
|
||||||
|
if (isError && pendingTxRef.current) {
|
||||||
|
updateTransaction(pendingTxRef.current.id, {
|
||||||
|
status: 'failed',
|
||||||
|
error: txError?.message || 'Transaction failed'
|
||||||
|
})
|
||||||
|
showToast('error', t('toast.txFailed'))
|
||||||
|
pendingTxRef.current = null
|
||||||
|
}
|
||||||
|
}, [isError])
|
||||||
|
|
||||||
|
// Handle write error
|
||||||
|
useEffect(() => {
|
||||||
|
if (writeError) {
|
||||||
|
const errorMsg = parseError(writeError)
|
||||||
|
showToast('error', errorMsg)
|
||||||
|
if (pendingTxRef.current) {
|
||||||
|
updateTransaction(pendingTxRef.current.id, { status: 'failed', error: errorMsg })
|
||||||
|
pendingTxRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [writeError])
|
||||||
|
|
||||||
|
const parseError = (error: any): string => {
|
||||||
|
const msg = error?.message || error?.toString() || 'Unknown error'
|
||||||
|
if (msg.includes('User rejected') || msg.includes('user rejected')) {
|
||||||
|
return t('toast.userRejected')
|
||||||
|
}
|
||||||
|
if (msg.includes('insufficient funds') || msg.includes('InsufficientBalance')) {
|
||||||
|
return t('toast.insufficientBalance')
|
||||||
|
}
|
||||||
|
if (msg.includes('CooldownNotPassed')) {
|
||||||
|
return t('lp.cooldownNotPassed')
|
||||||
|
}
|
||||||
|
if (msg.includes('InsufficientOutput')) {
|
||||||
|
return t('lp.insufficientOutput')
|
||||||
|
}
|
||||||
|
const match = msg.match(/error[:\s]+(\w+)/i)
|
||||||
|
if (match) return match[1]
|
||||||
|
return msg.slice(0, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordTx = (type: TransactionType, amount?: string, token?: string) => {
|
||||||
|
const id = addTransaction({
|
||||||
|
type,
|
||||||
|
hash: '',
|
||||||
|
status: 'pending',
|
||||||
|
amount,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
pendingTxRef.current = { id, type, amount }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve token for add liquidity
|
||||||
|
const handleApproveToken = () => {
|
||||||
|
if (!address) return
|
||||||
|
recordTx('approve', undefined, getTokenSymbol(addLiquidityForm.token))
|
||||||
|
writeContract({
|
||||||
|
address: addLiquidityForm.token as `0x${string}`,
|
||||||
|
abi: WUSD_ABI,
|
||||||
|
functionName: 'approve',
|
||||||
|
args: [CONTRACTS.YT_REWARD_ROUTER, parseUnits('1000000000', 18)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve ytLP for remove liquidity
|
||||||
|
const handleApproveYtLP = () => {
|
||||||
|
if (!address) return
|
||||||
|
recordTx('approve', undefined, 'ytLP')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.YT_LP_TOKEN,
|
||||||
|
abi: YT_LP_TOKEN_ABI,
|
||||||
|
functionName: 'approve',
|
||||||
|
args: [CONTRACTS.YT_REWARD_ROUTER, parseUnits('1000000000', 18)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add liquidity
|
||||||
|
const handleAddLiquidity = () => {
|
||||||
|
if (!address || !addLiquidityForm.amount) return
|
||||||
|
const amount = parseUnits(addLiquidityForm.amount, 18)
|
||||||
|
// For simplicity, set minUsdy and minYtLP to 0 (user accepts any slippage)
|
||||||
|
// In production, calculate based on current prices and addLiquidityForm.slippage
|
||||||
|
const minUsdy = BigInt(0)
|
||||||
|
const minYtLP = BigInt(0)
|
||||||
|
|
||||||
|
recordTx('buy', addLiquidityForm.amount, getTokenSymbol(addLiquidityForm.token))
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.YT_REWARD_ROUTER,
|
||||||
|
abi: YT_REWARD_ROUTER_ABI,
|
||||||
|
functionName: 'addLiquidity',
|
||||||
|
args: [addLiquidityForm.token as `0x${string}`, amount, minUsdy, minYtLP],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove liquidity
|
||||||
|
const handleRemoveLiquidity = () => {
|
||||||
|
if (!address || !removeLiquidityForm.amount) return
|
||||||
|
const ytLPAmount = parseUnits(removeLiquidityForm.amount, 18)
|
||||||
|
// For simplicity, set minOut to 0
|
||||||
|
const minOut = BigInt(0)
|
||||||
|
|
||||||
|
recordTx('sell', removeLiquidityForm.amount, 'ytLP')
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.YT_REWARD_ROUTER,
|
||||||
|
abi: YT_REWARD_ROUTER_ABI,
|
||||||
|
functionName: 'removeLiquidity',
|
||||||
|
args: [removeLiquidityForm.token as `0x${string}`, ytLPAmount, minOut, address],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap YT tokens
|
||||||
|
const handleSwap = () => {
|
||||||
|
if (!address || !swapForm.amount) return
|
||||||
|
const amountIn = parseUnits(swapForm.amount, 18)
|
||||||
|
// For simplicity, set minOut to 0
|
||||||
|
const minOut = BigInt(0)
|
||||||
|
|
||||||
|
recordTx('buy', swapForm.amount, getTokenSymbol(swapForm.tokenIn))
|
||||||
|
writeContract({
|
||||||
|
address: CONTRACTS.YT_REWARD_ROUTER,
|
||||||
|
abi: YT_REWARD_ROUTER_ABI,
|
||||||
|
functionName: 'swapYT',
|
||||||
|
args: [
|
||||||
|
swapForm.tokenIn as `0x${string}`,
|
||||||
|
swapForm.tokenOut as `0x${string}`,
|
||||||
|
amountIn,
|
||||||
|
minOut,
|
||||||
|
address
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokenSymbol = (tokenAddress: string): string => {
|
||||||
|
const token = POOL_TOKENS.find(t => t.address.toLowerCase() === tokenAddress.toLowerCase())
|
||||||
|
return token?.symbol || 'Unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate cooldown remaining time
|
||||||
|
const getCooldownRemaining = () => {
|
||||||
|
if (!lastAddedAt || !cooldownDuration) return 0
|
||||||
|
const cooldownEnd = Number(lastAddedAt) + Number(cooldownDuration)
|
||||||
|
const now = Math.floor(Date.now() / 1000)
|
||||||
|
return Math.max(0, cooldownEnd - now)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatCooldown = (seconds: number) => {
|
||||||
|
if (seconds <= 0) return t('lp.noCooldown')
|
||||||
|
const mins = Math.floor(seconds / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
return `${mins}m ${secs}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
const needsApproval = () => {
|
||||||
|
if (!tokenAllowance || !addLiquidityForm.amount) return false
|
||||||
|
try {
|
||||||
|
const amount = parseUnits(addLiquidityForm.amount, 18)
|
||||||
|
return tokenAllowance < amount
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const needsYtLPApproval = () => {
|
||||||
|
if (!ytLPAllowance || !removeLiquidityForm.amount) return false
|
||||||
|
try {
|
||||||
|
const amount = parseUnits(removeLiquidityForm.amount, 18)
|
||||||
|
return ytLPAllowance < amount
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
return (
|
||||||
|
<div className="panel">
|
||||||
|
<h2>{t('lp.title')}</h2>
|
||||||
|
<p className="text-muted">{t('common.connectFirst')}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="panel">
|
||||||
|
<h2>{t('lp.title')}</h2>
|
||||||
|
|
||||||
|
{/* Pool Info */}
|
||||||
|
<div className="pool-info">
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.rewardRouter')}:</span>
|
||||||
|
<code>{CONTRACTS.YT_REWARD_ROUTER}</code>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.ytLPToken')}:</span>
|
||||||
|
<code>{CONTRACTS.YT_LP_TOKEN}</code>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.poolAUM')}:</span>
|
||||||
|
<strong>{aumInUsdy ? formatUnits(aumInUsdy, 18) : '0'} USDY</strong>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.ytLPPrice')}:</span>
|
||||||
|
<strong>{ytLPPrice ? formatUnits(ytLPPrice, 30) : '1'} USDY</strong>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.totalSupply')}:</span>
|
||||||
|
<strong>{ytLPTotalSupply ? formatUnits(ytLPTotalSupply, 18) : '0'} ytLP</strong>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.yourBalance')}:</span>
|
||||||
|
<strong>{ytLPBalance ? formatUnits(ytLPBalance, 18) : '0'} ytLP</strong>
|
||||||
|
</div>
|
||||||
|
<div className="info-row">
|
||||||
|
<span>{t('lp.cooldownRemaining')}:</span>
|
||||||
|
<strong>{formatCooldown(getCooldownRemaining())}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tab Navigation */}
|
||||||
|
<div className="lp-tabs">
|
||||||
|
<button
|
||||||
|
className={`tab-btn ${activeTab === 'add' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('add')}
|
||||||
|
>
|
||||||
|
{t('lp.addLiquidity')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tab-btn ${activeTab === 'remove' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('remove')}
|
||||||
|
>
|
||||||
|
{t('lp.removeLiquidity')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tab-btn ${activeTab === 'swap' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('swap')}
|
||||||
|
>
|
||||||
|
{t('lp.swapTokens')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Add Liquidity Form */}
|
||||||
|
{activeTab === 'add' && (
|
||||||
|
<div className="section">
|
||||||
|
<h3>{t('lp.addLiquidity')}</h3>
|
||||||
|
<p className="text-muted">{t('lp.addLiquidityDesc')}</p>
|
||||||
|
|
||||||
|
<div className="form-grid">
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.selectToken')}</label>
|
||||||
|
<select
|
||||||
|
value={addLiquidityForm.token}
|
||||||
|
onChange={(e) => setAddLiquidityForm({ ...addLiquidityForm, token: e.target.value })}
|
||||||
|
className="input"
|
||||||
|
>
|
||||||
|
{POOL_TOKENS.map((token) => (
|
||||||
|
<option key={token.address} value={token.address}>
|
||||||
|
{token.symbol} - {token.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.amount')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={addLiquidityForm.amount}
|
||||||
|
onChange={(e) => setAddLiquidityForm({ ...addLiquidityForm, amount: e.target.value })}
|
||||||
|
placeholder="0.0"
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.slippage')} (%)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={addLiquidityForm.slippage}
|
||||||
|
onChange={(e) => setAddLiquidityForm({ ...addLiquidityForm, slippage: e.target.value })}
|
||||||
|
placeholder="0.5"
|
||||||
|
className="input"
|
||||||
|
step="0.1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{needsApproval() ? (
|
||||||
|
<button
|
||||||
|
onClick={handleApproveToken}
|
||||||
|
disabled={isPending || isConfirming}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? t('common.processing') : t('lp.approveToken')}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleAddLiquidity}
|
||||||
|
disabled={isPending || isConfirming || !addLiquidityForm.amount}
|
||||||
|
className="btn btn-primary"
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? t('common.processing') : t('lp.addLiquidity')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Remove Liquidity Form */}
|
||||||
|
{activeTab === 'remove' && (
|
||||||
|
<div className="section">
|
||||||
|
<h3>{t('lp.removeLiquidity')}</h3>
|
||||||
|
<p className="text-muted">{t('lp.removeLiquidityDesc')}</p>
|
||||||
|
|
||||||
|
{getCooldownRemaining() > 0 && (
|
||||||
|
<div className="warning-box">
|
||||||
|
{t('lp.cooldownWarning', { time: formatCooldown(getCooldownRemaining()) })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="form-grid">
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.outputToken')}</label>
|
||||||
|
<select
|
||||||
|
value={removeLiquidityForm.token}
|
||||||
|
onChange={(e) => setRemoveLiquidityForm({ ...removeLiquidityForm, token: e.target.value })}
|
||||||
|
className="input"
|
||||||
|
>
|
||||||
|
{POOL_TOKENS.map((token) => (
|
||||||
|
<option key={token.address} value={token.address}>
|
||||||
|
{token.symbol} - {token.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.ytLPAmount')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={removeLiquidityForm.amount}
|
||||||
|
onChange={(e) => setRemoveLiquidityForm({ ...removeLiquidityForm, amount: e.target.value })}
|
||||||
|
placeholder="0.0"
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-link"
|
||||||
|
onClick={() => setRemoveLiquidityForm({
|
||||||
|
...removeLiquidityForm,
|
||||||
|
amount: ytLPBalance ? formatUnits(ytLPBalance, 18) : '0'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t('lp.max')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.slippage')} (%)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={removeLiquidityForm.slippage}
|
||||||
|
onChange={(e) => setRemoveLiquidityForm({ ...removeLiquidityForm, slippage: e.target.value })}
|
||||||
|
placeholder="1"
|
||||||
|
className="input"
|
||||||
|
step="0.1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{needsYtLPApproval() ? (
|
||||||
|
<button
|
||||||
|
onClick={handleApproveYtLP}
|
||||||
|
disabled={isPending || isConfirming}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? t('common.processing') : t('lp.approveYtLP')}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleRemoveLiquidity}
|
||||||
|
disabled={isPending || isConfirming || !removeLiquidityForm.amount || getCooldownRemaining() > 0}
|
||||||
|
className="btn btn-primary"
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? t('common.processing') : t('lp.removeLiquidity')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Swap Form */}
|
||||||
|
{activeTab === 'swap' && (
|
||||||
|
<div className="section">
|
||||||
|
<h3>{t('lp.swapTokens')}</h3>
|
||||||
|
<p className="text-muted">{t('lp.swapDesc')}</p>
|
||||||
|
|
||||||
|
<div className="form-grid">
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.fromToken')}</label>
|
||||||
|
<select
|
||||||
|
value={swapForm.tokenIn}
|
||||||
|
onChange={(e) => setSwapForm({ ...swapForm, tokenIn: e.target.value })}
|
||||||
|
className="input"
|
||||||
|
>
|
||||||
|
{POOL_TOKENS.map((token) => (
|
||||||
|
<option key={token.address} value={token.address}>
|
||||||
|
{token.symbol}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.toToken')}</label>
|
||||||
|
<select
|
||||||
|
value={swapForm.tokenOut}
|
||||||
|
onChange={(e) => setSwapForm({ ...swapForm, tokenOut: e.target.value })}
|
||||||
|
className="input"
|
||||||
|
>
|
||||||
|
{POOL_TOKENS.map((token) => (
|
||||||
|
<option key={token.address} value={token.address}>
|
||||||
|
{token.symbol}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.amount')}</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={swapForm.amount}
|
||||||
|
onChange={(e) => setSwapForm({ ...swapForm, amount: e.target.value })}
|
||||||
|
placeholder="0.0"
|
||||||
|
className="input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('lp.slippage')} (%)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={swapForm.slippage}
|
||||||
|
onChange={(e) => setSwapForm({ ...swapForm, slippage: e.target.value })}
|
||||||
|
placeholder="0.5"
|
||||||
|
className="input"
|
||||||
|
step="0.1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleSwap}
|
||||||
|
disabled={isPending || isConfirming || !swapForm.amount || swapForm.tokenIn === swapForm.tokenOut}
|
||||||
|
className="btn btn-primary"
|
||||||
|
>
|
||||||
|
{isPending || isConfirming ? t('common.processing') : t('lp.swap')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Transaction History */}
|
||||||
|
<TransactionHistory transactions={transactions} onClear={clearHistory} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,7 +5,13 @@ 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,
|
||||||
}
|
},
|
||||||
|
// 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FACTORY_ABI = [
|
export const FACTORY_ABI = [
|
||||||
@@ -508,3 +514,254 @@ export const WUSD_ABI = [
|
|||||||
type: 'function'
|
type: 'function'
|
||||||
}
|
}
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
// YT Reward Router ABI - for addLiquidity, removeLiquidity, swapYT
|
||||||
|
export const YT_REWARD_ROUTER_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_token', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: '_amount', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_minUsdy', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_minYtLP', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'addLiquidity',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_tokenOut', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: '_ytLPAmount', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_minOut', type: 'uint256' },
|
||||||
|
{ internalType: 'address', name: '_receiver', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'removeLiquidity',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_tokenIn', type: 'address' },
|
||||||
|
{ internalType: 'address', name: '_tokenOut', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: '_amountIn', type: 'uint256' },
|
||||||
|
{ internalType: 'uint256', name: '_minOut', type: 'uint256' },
|
||||||
|
{ internalType: 'address', name: '_receiver', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'swapYT',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'getYtLPPrice',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_account', type: 'address' }],
|
||||||
|
name: 'getAccountValue',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'gov',
|
||||||
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'ytLP',
|
||||||
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'ytPoolManager',
|
||||||
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'ytVault',
|
||||||
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// YT LP Token ABI (ERC20)
|
||||||
|
export const YT_LP_TOKEN_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
|
||||||
|
name: 'balanceOf',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'totalSupply',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'decimals',
|
||||||
|
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'symbol',
|
||||||
|
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'name',
|
||||||
|
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: 'spender', type: 'address' },
|
||||||
|
{ internalType: 'uint256', name: 'value', type: 'uint256' }
|
||||||
|
],
|
||||||
|
name: 'approve',
|
||||||
|
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: 'owner', type: 'address' },
|
||||||
|
{ internalType: 'address', name: 'spender', type: 'address' }
|
||||||
|
],
|
||||||
|
name: 'allowance',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// YT Pool Manager ABI
|
||||||
|
export const YT_POOL_MANAGER_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'bool', name: '_maximise', type: 'bool' }],
|
||||||
|
name: 'getAumInUsdy',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'cooldownDuration',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
name: 'lastAddedAt',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'gov',
|
||||||
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// YT Vault ABI - for pool info
|
||||||
|
export const YT_VAULT_ABI = [
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'getAllWhitelistedTokens',
|
||||||
|
outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_token', type: 'address' }],
|
||||||
|
name: 'poolAmounts',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_token', type: 'address' }],
|
||||||
|
name: 'usdyAmounts',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_token', type: 'address' }],
|
||||||
|
name: 'tokenWeights',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'totalTokenWeights',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_token', type: 'address' }],
|
||||||
|
name: 'whitelistedTokens',
|
||||||
|
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: '_token', type: 'address' },
|
||||||
|
{ internalType: 'bool', name: '_maximise', type: 'bool' }
|
||||||
|
],
|
||||||
|
name: 'getPrice',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_token', type: 'address' }],
|
||||||
|
name: 'getMinPrice',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [{ internalType: 'address', name: '_token', type: 'address' }],
|
||||||
|
name: 'getMaxPrice',
|
||||||
|
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [],
|
||||||
|
name: 'gov',
|
||||||
|
outputs: [{ internalType: 'address', name: '', type: 'address' }],
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"wusd": "WUSD",
|
"wusd": "WUSD",
|
||||||
"vaultTrading": "Vault Trading",
|
"vaultTrading": "Vault Trading",
|
||||||
"factory": "Factory"
|
"factory": "Factory",
|
||||||
|
"lpPool": "LP Pool"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"title": "YT Asset Test"
|
"title": "YT Asset Test"
|
||||||
@@ -147,5 +148,36 @@
|
|||||||
"updatePriceNotOwnerDesc": "Non-owner calls updateVaultPrices",
|
"updatePriceNotOwnerDesc": "Non-owner calls updateVaultPrices",
|
||||||
"setManagerNotOwner": "Set Manager (Not Owner)",
|
"setManagerNotOwner": "Set Manager (Not Owner)",
|
||||||
"setManagerNotOwnerDesc": "Non-owner calls setVaultManager"
|
"setManagerNotOwnerDesc": "Non-owner calls setVaultManager"
|
||||||
|
},
|
||||||
|
"lp": {
|
||||||
|
"title": "YT Liquidity Pool",
|
||||||
|
"rewardRouter": "Reward Router Contract",
|
||||||
|
"ytLPToken": "ytLP Token",
|
||||||
|
"poolAUM": "Pool AUM",
|
||||||
|
"ytLPPrice": "ytLP Price",
|
||||||
|
"totalSupply": "Total Supply",
|
||||||
|
"yourBalance": "Your Balance",
|
||||||
|
"cooldownRemaining": "Cooldown Remaining",
|
||||||
|
"noCooldown": "No cooldown",
|
||||||
|
"addLiquidity": "Add Liquidity",
|
||||||
|
"addLiquidityDesc": "Deposit YT tokens or WUSD to receive ytLP tokens",
|
||||||
|
"removeLiquidity": "Remove Liquidity",
|
||||||
|
"removeLiquidityDesc": "Burn ytLP to get tokens back",
|
||||||
|
"swapTokens": "Swap Tokens",
|
||||||
|
"swapDesc": "Swap between YT tokens and WUSD in the pool",
|
||||||
|
"selectToken": "Select Token",
|
||||||
|
"amount": "Amount",
|
||||||
|
"slippage": "Slippage Tolerance",
|
||||||
|
"approveToken": "Approve Token",
|
||||||
|
"approveYtLP": "Approve ytLP",
|
||||||
|
"outputToken": "Output Token",
|
||||||
|
"ytLPAmount": "ytLP Amount",
|
||||||
|
"max": "Max",
|
||||||
|
"fromToken": "From Token",
|
||||||
|
"toToken": "To Token",
|
||||||
|
"swap": "Swap",
|
||||||
|
"cooldownNotPassed": "Cooldown not passed, please try later",
|
||||||
|
"insufficientOutput": "Insufficient output amount",
|
||||||
|
"cooldownWarning": "Cooldown remaining {{time}}, cannot remove liquidity yet"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"wusd": "WUSD",
|
"wusd": "WUSD",
|
||||||
"vaultTrading": "金库交易",
|
"vaultTrading": "金库交易",
|
||||||
"factory": "工厂管理"
|
"factory": "工厂管理",
|
||||||
|
"lpPool": "LP 流动池"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"title": "YT 资产测试"
|
"title": "YT 资产测试"
|
||||||
@@ -147,5 +148,36 @@
|
|||||||
"updatePriceNotOwnerDesc": "非 Owner 调用 updateVaultPrices",
|
"updatePriceNotOwnerDesc": "非 Owner 调用 updateVaultPrices",
|
||||||
"setManagerNotOwner": "设置Manager(非Owner)",
|
"setManagerNotOwner": "设置Manager(非Owner)",
|
||||||
"setManagerNotOwnerDesc": "非 Owner 调用 setVaultManager"
|
"setManagerNotOwnerDesc": "非 Owner 调用 setVaultManager"
|
||||||
|
},
|
||||||
|
"lp": {
|
||||||
|
"title": "YT 流动性池",
|
||||||
|
"rewardRouter": "奖励路由合约",
|
||||||
|
"ytLPToken": "ytLP 代币",
|
||||||
|
"poolAUM": "池子总资产(AUM)",
|
||||||
|
"ytLPPrice": "ytLP 价格",
|
||||||
|
"totalSupply": "总供应量",
|
||||||
|
"yourBalance": "你的余额",
|
||||||
|
"cooldownRemaining": "冷却时间剩余",
|
||||||
|
"noCooldown": "无冷却",
|
||||||
|
"addLiquidity": "添加流动性",
|
||||||
|
"addLiquidityDesc": "存入 YT 代币或 WUSD 获得 ytLP 凭证",
|
||||||
|
"removeLiquidity": "移除流动性",
|
||||||
|
"removeLiquidityDesc": "销毁 ytLP 获取代币",
|
||||||
|
"swapTokens": "代币互换",
|
||||||
|
"swapDesc": "在池内交换 YT 代币和 WUSD",
|
||||||
|
"selectToken": "选择代币",
|
||||||
|
"amount": "数量",
|
||||||
|
"slippage": "滑点容忍度",
|
||||||
|
"approveToken": "授权代币",
|
||||||
|
"approveYtLP": "授权 ytLP",
|
||||||
|
"outputToken": "输出代币",
|
||||||
|
"ytLPAmount": "ytLP 数量",
|
||||||
|
"max": "最大",
|
||||||
|
"fromToken": "输入代币",
|
||||||
|
"toToken": "输出代币",
|
||||||
|
"swap": "交换",
|
||||||
|
"cooldownNotPassed": "冷却期未过,请稍后再试",
|
||||||
|
"insufficientOutput": "输出金额不足",
|
||||||
|
"cooldownWarning": "冷却期剩余 {{time}},暂时无法移除流动性"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user