refactor(pool-accounts): 移除硬编码钱包名,改为从后端 API 动态获取
- 后端: 新增 GET /admin/pool-accounts 接口,返回从 .env 配置的池账户列表 - 前端: 新增 usePoolAccounts hook,动态渲染池账户卡片 - 前端: 提取 PoolAccountCard 子组件,消除重复代码 - 移除前端硬编码的 BURN_POOL_WALLET_NAME / MINING_POOL_WALLET_NAME 常量 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7972163af6
commit
8cfd107a92
|
|
@ -53,6 +53,17 @@ export class PoolAccountController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: '获取已配置的池账户列表' })
|
||||||
|
async listPoolAccounts() {
|
||||||
|
return Object.entries(this.walletNameMap).map(([walletName, info]) => ({
|
||||||
|
walletName,
|
||||||
|
name: info.name,
|
||||||
|
walletPoolType: info.walletPoolType,
|
||||||
|
blockchainPoolType: info.blockchainPoolType,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
@Get(':walletName/balance')
|
@Get(':walletName/balance')
|
||||||
@ApiOperation({ summary: '获取池账户余额(代理到 wallet-service + blockchain-service)' })
|
@ApiOperation({ summary: '获取池账户余额(代理到 wallet-service + blockchain-service)' })
|
||||||
@ApiParam({ name: 'walletName', description: '池钱包名称(MPC用户名)' })
|
@ApiParam({ name: 'walletName', description: '池钱包名称(MPC用户名)' })
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import { PageHeader } from '@/components/layout/page-header';
|
import { PageHeader } from '@/components/layout/page-header';
|
||||||
import { useConfigs, useUpdateConfig, useTransferEnabled, useSetTransferEnabled, useMiningStatus, useActivateMining, useDeactivateMining, useP2pTransferFee, useSetP2pTransferFee, usePoolAccountBalance, usePoolAccountBlockchainWithdraw, usePoolAccountCentralizedDeposit } from '@/features/configs/hooks/use-configs';
|
import { useConfigs, useUpdateConfig, useTransferEnabled, useSetTransferEnabled, useMiningStatus, useActivateMining, useDeactivateMining, useP2pTransferFee, useSetP2pTransferFee, usePoolAccounts, usePoolAccountBalance, usePoolAccountBlockchainWithdraw, usePoolAccountCentralizedDeposit } from '@/features/configs/hooks/use-configs';
|
||||||
|
import type { PoolAccountInfo, PoolAccountBalance } from '@/features/configs/api/configs.api';
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
|
@ -25,9 +26,11 @@ import { Badge } from '@/components/ui/badge';
|
||||||
import { Pencil, Save, X, Play, Pause, AlertCircle, CheckCircle2, Loader2, Wallet, PlusCircle, MinusCircle, Copy, Check, Flame, HardHat } from 'lucide-react';
|
import { Pencil, Save, X, Play, Pause, AlertCircle, CheckCircle2, Loader2, Wallet, PlusCircle, MinusCircle, Copy, Check, Flame, HardHat } from 'lucide-react';
|
||||||
import type { SystemConfig } from '@/types/config';
|
import type { SystemConfig } from '@/types/config';
|
||||||
|
|
||||||
// 池账户钱包名(与后端 .env 中的 BURN_POOL_WALLET_USERNAME / MINING_POOL_WALLET_USERNAME 对应)
|
// 根据 blockchainPoolType 映射视觉属性
|
||||||
const BURN_POOL_WALLET_NAME = 'wallet-22fd661f';
|
const poolVisualMap: Record<string, { icon: typeof Flame; color: string }> = {
|
||||||
const MINING_POOL_WALLET_NAME = 'wallet-974e78f5';
|
BURN_POOL: { icon: Flame, color: 'orange' },
|
||||||
|
MINING_POOL: { icon: HardHat, color: 'blue' },
|
||||||
|
};
|
||||||
|
|
||||||
const categoryLabels: Record<string, string> = {
|
const categoryLabels: Record<string, string> = {
|
||||||
mining: '挖矿配置',
|
mining: '挖矿配置',
|
||||||
|
|
@ -36,43 +39,20 @@ const categoryLabels: Record<string, string> = {
|
||||||
system: '系统配置',
|
system: '系统配置',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ConfigsPage() {
|
// 池账户卡片子组件
|
||||||
const { data: configs, isLoading } = useConfigs();
|
function PoolAccountCard({ pool }: { pool: PoolAccountInfo }) {
|
||||||
const { data: transferEnabled, isLoading: transferLoading } = useTransferEnabled();
|
const { data: balance, isLoading } = usePoolAccountBalance(pool.walletName);
|
||||||
const { data: miningStatus, isLoading: miningLoading } = useMiningStatus();
|
|
||||||
const updateConfig = useUpdateConfig();
|
|
||||||
const setTransferEnabled = useSetTransferEnabled();
|
|
||||||
const activateMining = useActivateMining();
|
|
||||||
const deactivateMining = useDeactivateMining();
|
|
||||||
|
|
||||||
const { data: feeConfig, isLoading: feeLoading } = useP2pTransferFee();
|
|
||||||
const setP2pTransferFee = useSetP2pTransferFee();
|
|
||||||
|
|
||||||
const { data: burnPoolBalance, isLoading: burnPoolLoading } = usePoolAccountBalance(BURN_POOL_WALLET_NAME);
|
|
||||||
const { data: miningPoolBalance, isLoading: miningPoolLoading } = usePoolAccountBalance(MINING_POOL_WALLET_NAME);
|
|
||||||
const blockchainWithdrawMutation = usePoolAccountBlockchainWithdraw();
|
const blockchainWithdrawMutation = usePoolAccountBlockchainWithdraw();
|
||||||
const centralizedDepositMutation = usePoolAccountCentralizedDeposit();
|
const centralizedDepositMutation = usePoolAccountCentralizedDeposit();
|
||||||
|
|
||||||
const [editingConfig, setEditingConfig] = useState<SystemConfig | null>(null);
|
const [withdrawAddress, setWithdrawAddress] = useState('');
|
||||||
const [editValue, setEditValue] = useState('');
|
const [withdrawAmount, setWithdrawAmount] = useState('');
|
||||||
const [feeValue, setFeeValue] = useState('');
|
const [depositAmount, setDepositAmount] = useState('');
|
||||||
const [minAmountValue, setMinAmountValue] = useState('');
|
|
||||||
|
|
||||||
// 池账户状态
|
|
||||||
const [burnWithdrawAddress, setBurnWithdrawAddress] = useState('');
|
|
||||||
const [burnWithdrawAmount, setBurnWithdrawAmount] = useState('');
|
|
||||||
const [miningWithdrawAddress, setMiningWithdrawAddress] = useState('');
|
|
||||||
const [miningWithdrawAmount, setMiningWithdrawAmount] = useState('');
|
|
||||||
const [burnDepositAmount, setBurnDepositAmount] = useState('');
|
|
||||||
const [miningDepositAmount, setMiningDepositAmount] = useState('');
|
|
||||||
const [copiedAddress, setCopiedAddress] = useState<string | null>(null);
|
const [copiedAddress, setCopiedAddress] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const visual = poolVisualMap[pool.blockchainPoolType] || { icon: Wallet, color: 'gray' };
|
||||||
if (feeConfig) {
|
const Icon = visual.icon;
|
||||||
setFeeValue(feeConfig.fee);
|
const colorClass = visual.color === 'orange' ? 'text-orange-500' : visual.color === 'blue' ? 'text-blue-500' : 'text-gray-500';
|
||||||
setMinAmountValue(feeConfig.minTransferAmount);
|
|
||||||
}
|
|
||||||
}, [feeConfig]);
|
|
||||||
|
|
||||||
const handleCopyAddress = async (address: string) => {
|
const handleCopyAddress = async (address: string) => {
|
||||||
await navigator.clipboard.writeText(address);
|
await navigator.clipboard.writeText(address);
|
||||||
|
|
@ -87,6 +67,217 @@ export default function ConfigsPage() {
|
||||||
return num.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
return num.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
<Icon className={`h-5 w-5 ${colorClass}`} />
|
||||||
|
{pool.name}
|
||||||
|
</CardTitle>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
2-of-3 门限
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<CardDescription>
|
||||||
|
钱包: {pool.walletName}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{isLoading ? (
|
||||||
|
<Skeleton className="h-20 w-full" />
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">总余额</p>
|
||||||
|
<p className="text-2xl font-bold">{formatBalance(balance?.balance)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">可用余额</p>
|
||||||
|
<p className={`text-2xl font-bold ${colorClass}`}>{formatBalance(balance?.availableBalance)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">冻结中</p>
|
||||||
|
<p className="text-lg text-muted-foreground">{formatBalance(balance?.frozenBalance)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-2 pt-4 border-t">
|
||||||
|
{/* 充值(中心化 + 区块链) */}
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button size="sm" variant="outline">
|
||||||
|
<PlusCircle className="h-4 w-4 mr-1" />
|
||||||
|
充值
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>充值 — {pool.name}</DialogTitle>
|
||||||
|
<DialogDescription>选择充值方式向{pool.name}充值</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Tabs defaultValue="centralized" className="w-full">
|
||||||
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
|
<TabsTrigger value="centralized">中心化充值</TabsTrigger>
|
||||||
|
<TabsTrigger value="blockchain">区块链充值</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="centralized" className="space-y-4 pt-4">
|
||||||
|
<div>
|
||||||
|
<Label>充值金额</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={depositAmount}
|
||||||
|
onChange={(e) => setDepositAmount(e.target.value)}
|
||||||
|
placeholder="请输入金额"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
centralizedDepositMutation.mutate({
|
||||||
|
walletName: pool.walletName,
|
||||||
|
amount: depositAmount,
|
||||||
|
});
|
||||||
|
setDepositAmount('');
|
||||||
|
}}
|
||||||
|
disabled={centralizedDepositMutation.isPending || !depositAmount}
|
||||||
|
>
|
||||||
|
{centralizedDepositMutation.isPending ? '处理中...' : '确认充值'}
|
||||||
|
</Button>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
||||||
|
<div className="text-sm text-muted-foreground text-center">
|
||||||
|
向以下地址转入 <strong>fUSDT</strong>(积分值代币)
|
||||||
|
</div>
|
||||||
|
{balance?.walletAddress ? (
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<div className="p-4 bg-white rounded-lg">
|
||||||
|
<QRCodeSVG value={balance.walletAddress} size={180} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label className="text-xs text-muted-foreground">钱包地址 (Kava EVM)</Label>
|
||||||
|
<div className="flex items-center gap-2 mt-1">
|
||||||
|
<code className="flex-1 text-xs bg-muted p-2 rounded break-all">
|
||||||
|
{balance.walletAddress}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleCopyAddress(balance.walletAddress)}
|
||||||
|
>
|
||||||
|
{copiedAddress === balance.walletAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded w-full">
|
||||||
|
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||||||
|
转账后系统将自动检测并入账(约需12个区块确认)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-muted-foreground py-4">
|
||||||
|
<AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" />
|
||||||
|
<p className="text-sm">钱包地址未配置</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* 区块链提现 */}
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button size="sm" variant="outline">
|
||||||
|
<MinusCircle className="h-4 w-4 mr-1" />
|
||||||
|
提现
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>区块链提现 — {pool.name}</DialogTitle>
|
||||||
|
<DialogDescription>转账 fUSDT(积分值代币)到指定地址</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label>目标地址 (Kava EVM)</Label>
|
||||||
|
<Input
|
||||||
|
value={withdrawAddress}
|
||||||
|
onChange={(e) => setWithdrawAddress(e.target.value)}
|
||||||
|
placeholder="0x..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>提现金额</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={withdrawAmount}
|
||||||
|
onChange={(e) => setWithdrawAmount(e.target.value)}
|
||||||
|
placeholder="请输入金额"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
blockchainWithdrawMutation.mutate({
|
||||||
|
walletName: pool.walletName,
|
||||||
|
toAddress: withdrawAddress,
|
||||||
|
amount: withdrawAmount,
|
||||||
|
});
|
||||||
|
setWithdrawAddress('');
|
||||||
|
setWithdrawAmount('');
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
blockchainWithdrawMutation.isPending ||
|
||||||
|
!withdrawAddress ||
|
||||||
|
!withdrawAmount ||
|
||||||
|
!withdrawAddress.startsWith('0x')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{blockchainWithdrawMutation.isPending ? '链上转账中...' : '确认区块链提现'}
|
||||||
|
</Button>
|
||||||
|
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded">
|
||||||
|
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||||||
|
区块链提现将通过 2-of-3 门限签名从{pool.name}钱包转账到目标地址
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ConfigsPage() {
|
||||||
|
const { data: configs, isLoading } = useConfigs();
|
||||||
|
const { data: transferEnabled, isLoading: transferLoading } = useTransferEnabled();
|
||||||
|
const { data: miningStatus, isLoading: miningLoading } = useMiningStatus();
|
||||||
|
const updateConfig = useUpdateConfig();
|
||||||
|
const setTransferEnabled = useSetTransferEnabled();
|
||||||
|
const activateMining = useActivateMining();
|
||||||
|
const deactivateMining = useDeactivateMining();
|
||||||
|
|
||||||
|
const { data: feeConfig, isLoading: feeLoading } = useP2pTransferFee();
|
||||||
|
const setP2pTransferFee = useSetP2pTransferFee();
|
||||||
|
|
||||||
|
const { data: poolAccounts, isLoading: poolAccountsLoading } = usePoolAccounts();
|
||||||
|
|
||||||
|
const [editingConfig, setEditingConfig] = useState<SystemConfig | null>(null);
|
||||||
|
const [editValue, setEditValue] = useState('');
|
||||||
|
const [feeValue, setFeeValue] = useState('');
|
||||||
|
const [minAmountValue, setMinAmountValue] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (feeConfig) {
|
||||||
|
setFeeValue(feeConfig.fee);
|
||||||
|
setMinAmountValue(feeConfig.minTransferAmount);
|
||||||
|
}
|
||||||
|
}, [feeConfig]);
|
||||||
|
|
||||||
const handleEdit = (config: SystemConfig) => {
|
const handleEdit = (config: SystemConfig) => {
|
||||||
setEditingConfig(config);
|
setEditingConfig(config);
|
||||||
setEditValue(config.configValue);
|
setEditValue(config.configValue);
|
||||||
|
|
@ -235,374 +426,19 @@ export default function ConfigsPage() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 池账户管理 */}
|
{/* 池账户管理 — 动态渲染 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
{poolAccountsLoading ? (
|
||||||
{/* 100亿销毁池 */}
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Card>
|
<Card><CardContent className="p-6"><Skeleton className="h-48 w-full" /></CardContent></Card>
|
||||||
<CardHeader>
|
<Card><CardContent className="p-6"><Skeleton className="h-48 w-full" /></CardContent></Card>
|
||||||
<div className="flex items-center justify-between">
|
</div>
|
||||||
<CardTitle className="text-lg flex items-center gap-2">
|
) : poolAccounts && poolAccounts.length > 0 ? (
|
||||||
<Flame className="h-5 w-5 text-orange-500" />
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
100亿销毁池
|
{poolAccounts.map((pool) => (
|
||||||
</CardTitle>
|
<PoolAccountCard key={pool.walletName} pool={pool} />
|
||||||
<Badge variant="outline" className="text-xs">
|
))}
|
||||||
2-of-3 门限
|
</div>
|
||||||
</Badge>
|
) : null}
|
||||||
</div>
|
|
||||||
<CardDescription>
|
|
||||||
钱包: {BURN_POOL_WALLET_NAME}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{burnPoolLoading ? (
|
|
||||||
<Skeleton className="h-20 w-full" />
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">总余额</p>
|
|
||||||
<p className="text-2xl font-bold">{formatBalance(burnPoolBalance?.balance)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">可用余额</p>
|
|
||||||
<p className="text-2xl font-bold text-orange-500">{formatBalance(burnPoolBalance?.availableBalance)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">冻结中</p>
|
|
||||||
<p className="text-lg text-muted-foreground">{formatBalance(burnPoolBalance?.frozenBalance)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex gap-2 pt-4 border-t">
|
|
||||||
{/* 充值(中心化 + 区块链) */}
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button size="sm" variant="outline">
|
|
||||||
<PlusCircle className="h-4 w-4 mr-1" />
|
|
||||||
充值
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>充值 — 100亿销毁池</DialogTitle>
|
|
||||||
<DialogDescription>选择充值方式向销毁池充值</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<Tabs defaultValue="centralized" className="w-full">
|
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
|
||||||
<TabsTrigger value="centralized">中心化充值</TabsTrigger>
|
|
||||||
<TabsTrigger value="blockchain">区块链充值</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="centralized" className="space-y-4 pt-4">
|
|
||||||
<div>
|
|
||||||
<Label>充值金额</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={burnDepositAmount}
|
|
||||||
onChange={(e) => setBurnDepositAmount(e.target.value)}
|
|
||||||
placeholder="请输入金额"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => {
|
|
||||||
centralizedDepositMutation.mutate({
|
|
||||||
walletName: BURN_POOL_WALLET_NAME,
|
|
||||||
amount: burnDepositAmount,
|
|
||||||
});
|
|
||||||
setBurnDepositAmount('');
|
|
||||||
}}
|
|
||||||
disabled={centralizedDepositMutation.isPending || !burnDepositAmount}
|
|
||||||
>
|
|
||||||
{centralizedDepositMutation.isPending ? '处理中...' : '确认充值'}
|
|
||||||
</Button>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
|
||||||
<div className="text-sm text-muted-foreground text-center">
|
|
||||||
向以下地址转入 <strong>fUSDT</strong>(积分值代币)
|
|
||||||
</div>
|
|
||||||
{burnPoolBalance?.walletAddress ? (
|
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<div className="p-4 bg-white rounded-lg">
|
|
||||||
<QRCodeSVG value={burnPoolBalance.walletAddress} size={180} />
|
|
||||||
</div>
|
|
||||||
<div className="w-full">
|
|
||||||
<Label className="text-xs text-muted-foreground">钱包地址 (Kava EVM)</Label>
|
|
||||||
<div className="flex items-center gap-2 mt-1">
|
|
||||||
<code className="flex-1 text-xs bg-muted p-2 rounded break-all">
|
|
||||||
{burnPoolBalance.walletAddress}
|
|
||||||
</code>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleCopyAddress(burnPoolBalance.walletAddress)}
|
|
||||||
>
|
|
||||||
{copiedAddress === burnPoolBalance.walletAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded w-full">
|
|
||||||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
|
||||||
转账后系统将自动检测并入账(约需12个区块确认)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-center text-muted-foreground py-4">
|
|
||||||
<AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" />
|
|
||||||
<p className="text-sm">钱包地址未配置</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* 区块链提现 */}
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button size="sm" variant="outline">
|
|
||||||
<MinusCircle className="h-4 w-4 mr-1" />
|
|
||||||
提现
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>区块链提现 — 100亿销毁池</DialogTitle>
|
|
||||||
<DialogDescription>转账 fUSDT(积分值代币)到指定地址</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Label>目标地址 (Kava EVM)</Label>
|
|
||||||
<Input
|
|
||||||
value={burnWithdrawAddress}
|
|
||||||
onChange={(e) => setBurnWithdrawAddress(e.target.value)}
|
|
||||||
placeholder="0x..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label>提现金额</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={burnWithdrawAmount}
|
|
||||||
onChange={(e) => setBurnWithdrawAmount(e.target.value)}
|
|
||||||
placeholder="请输入金额"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => {
|
|
||||||
blockchainWithdrawMutation.mutate({
|
|
||||||
walletName: BURN_POOL_WALLET_NAME,
|
|
||||||
toAddress: burnWithdrawAddress,
|
|
||||||
amount: burnWithdrawAmount,
|
|
||||||
});
|
|
||||||
setBurnWithdrawAddress('');
|
|
||||||
setBurnWithdrawAmount('');
|
|
||||||
}}
|
|
||||||
disabled={
|
|
||||||
blockchainWithdrawMutation.isPending ||
|
|
||||||
!burnWithdrawAddress ||
|
|
||||||
!burnWithdrawAmount ||
|
|
||||||
!burnWithdrawAddress.startsWith('0x')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{blockchainWithdrawMutation.isPending ? '链上转账中...' : '确认区块链提现'}
|
|
||||||
</Button>
|
|
||||||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded">
|
|
||||||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
|
||||||
区块链提现将通过 2-of-3 门限签名从销毁池钱包转账到目标地址
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* 200万挖矿池 */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<CardTitle className="text-lg flex items-center gap-2">
|
|
||||||
<HardHat className="h-5 w-5 text-blue-500" />
|
|
||||||
200万挖矿池
|
|
||||||
</CardTitle>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
2-of-3 门限
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<CardDescription>
|
|
||||||
钱包: {MINING_POOL_WALLET_NAME}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{miningPoolLoading ? (
|
|
||||||
<Skeleton className="h-20 w-full" />
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">总余额</p>
|
|
||||||
<p className="text-2xl font-bold">{formatBalance(miningPoolBalance?.balance)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">可用余额</p>
|
|
||||||
<p className="text-2xl font-bold text-blue-500">{formatBalance(miningPoolBalance?.availableBalance)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">冻结中</p>
|
|
||||||
<p className="text-lg text-muted-foreground">{formatBalance(miningPoolBalance?.frozenBalance)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex gap-2 pt-4 border-t">
|
|
||||||
{/* 充值(中心化 + 区块链) */}
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button size="sm" variant="outline">
|
|
||||||
<PlusCircle className="h-4 w-4 mr-1" />
|
|
||||||
充值
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>充值 — 200万挖矿池</DialogTitle>
|
|
||||||
<DialogDescription>选择充值方式向挖矿池充值</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<Tabs defaultValue="centralized" className="w-full">
|
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
|
||||||
<TabsTrigger value="centralized">中心化充值</TabsTrigger>
|
|
||||||
<TabsTrigger value="blockchain">区块链充值</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="centralized" className="space-y-4 pt-4">
|
|
||||||
<div>
|
|
||||||
<Label>充值金额</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={miningDepositAmount}
|
|
||||||
onChange={(e) => setMiningDepositAmount(e.target.value)}
|
|
||||||
placeholder="请输入金额"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => {
|
|
||||||
centralizedDepositMutation.mutate({
|
|
||||||
walletName: MINING_POOL_WALLET_NAME,
|
|
||||||
amount: miningDepositAmount,
|
|
||||||
});
|
|
||||||
setMiningDepositAmount('');
|
|
||||||
}}
|
|
||||||
disabled={centralizedDepositMutation.isPending || !miningDepositAmount}
|
|
||||||
>
|
|
||||||
{centralizedDepositMutation.isPending ? '处理中...' : '确认充值'}
|
|
||||||
</Button>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
|
||||||
<div className="text-sm text-muted-foreground text-center">
|
|
||||||
向以下地址转入 <strong>fUSDT</strong>(积分值代币)
|
|
||||||
</div>
|
|
||||||
{miningPoolBalance?.walletAddress ? (
|
|
||||||
<div className="flex flex-col items-center space-y-4">
|
|
||||||
<div className="p-4 bg-white rounded-lg">
|
|
||||||
<QRCodeSVG value={miningPoolBalance.walletAddress} size={180} />
|
|
||||||
</div>
|
|
||||||
<div className="w-full">
|
|
||||||
<Label className="text-xs text-muted-foreground">钱包地址 (Kava EVM)</Label>
|
|
||||||
<div className="flex items-center gap-2 mt-1">
|
|
||||||
<code className="flex-1 text-xs bg-muted p-2 rounded break-all">
|
|
||||||
{miningPoolBalance.walletAddress}
|
|
||||||
</code>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleCopyAddress(miningPoolBalance.walletAddress)}
|
|
||||||
>
|
|
||||||
{copiedAddress === miningPoolBalance.walletAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded w-full">
|
|
||||||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
|
||||||
转账后系统将自动检测并入账(约需12个区块确认)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-center text-muted-foreground py-4">
|
|
||||||
<AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" />
|
|
||||||
<p className="text-sm">钱包地址未配置</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* 区块链提现 */}
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button size="sm" variant="outline">
|
|
||||||
<MinusCircle className="h-4 w-4 mr-1" />
|
|
||||||
提现
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>区块链提现 — 200万挖矿池</DialogTitle>
|
|
||||||
<DialogDescription>转账 fUSDT(积分值代币)到指定地址</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<Label>目标地址 (Kava EVM)</Label>
|
|
||||||
<Input
|
|
||||||
value={miningWithdrawAddress}
|
|
||||||
onChange={(e) => setMiningWithdrawAddress(e.target.value)}
|
|
||||||
placeholder="0x..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label>提现金额</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={miningWithdrawAmount}
|
|
||||||
onChange={(e) => setMiningWithdrawAmount(e.target.value)}
|
|
||||||
placeholder="请输入金额"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => {
|
|
||||||
blockchainWithdrawMutation.mutate({
|
|
||||||
walletName: MINING_POOL_WALLET_NAME,
|
|
||||||
toAddress: miningWithdrawAddress,
|
|
||||||
amount: miningWithdrawAmount,
|
|
||||||
});
|
|
||||||
setMiningWithdrawAddress('');
|
|
||||||
setMiningWithdrawAmount('');
|
|
||||||
}}
|
|
||||||
disabled={
|
|
||||||
blockchainWithdrawMutation.isPending ||
|
|
||||||
!miningWithdrawAddress ||
|
|
||||||
!miningWithdrawAmount ||
|
|
||||||
!miningWithdrawAddress.startsWith('0x')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{blockchainWithdrawMutation.isPending ? '链上转账中...' : '确认区块链提现'}
|
|
||||||
</Button>
|
|
||||||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded">
|
|
||||||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
|
||||||
区块链提现将通过 2-of-3 门限签名从挖矿池钱包转账到目标地址
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ export interface ContributionSyncStatus {
|
||||||
networkTotalContribution: string;
|
networkTotalContribution: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PoolAccountInfo {
|
||||||
|
walletName: string;
|
||||||
|
name: string;
|
||||||
|
walletPoolType: string;
|
||||||
|
blockchainPoolType: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PoolAccountBalance {
|
export interface PoolAccountBalance {
|
||||||
walletName: string;
|
walletName: string;
|
||||||
walletAddress: string;
|
walletAddress: string;
|
||||||
|
|
@ -74,6 +81,12 @@ export const configsApi = {
|
||||||
await apiClient.post('/configs/p2p-transfer-fee', { fee, minTransferAmount });
|
await apiClient.post('/configs/p2p-transfer-fee', { fee, minTransferAmount });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 获取已配置的池账户列表
|
||||||
|
getPoolAccounts: async (): Promise<PoolAccountInfo[]> => {
|
||||||
|
const response = await apiClient.get('/admin/pool-accounts');
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
|
||||||
// 获取池账户余额(通过 mining-admin-service 代理)
|
// 获取池账户余额(通过 mining-admin-service 代理)
|
||||||
getPoolAccountBalance: async (walletName: string): Promise<PoolAccountBalance> => {
|
getPoolAccountBalance: async (walletName: string): Promise<PoolAccountBalance> => {
|
||||||
const response = await apiClient.get(`/admin/pool-accounts/${walletName}/balance`);
|
const response = await apiClient.get(`/admin/pool-accounts/${walletName}/balance`);
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,13 @@ export function useSetP2pTransferFee() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePoolAccounts() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['configs', 'pool-accounts'],
|
||||||
|
queryFn: () => configsApi.getPoolAccounts(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function usePoolAccountBalance(walletName: string) {
|
export function usePoolAccountBalance(walletName: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['configs', 'pool-account', walletName],
|
queryKey: ['configs', 'pool-account', walletName],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue