feat(mining-admin): 配置管理新增100亿销毁池和200万挖矿池账户
在配置管理页面新增两个池账户卡片,UI 风格对齐做市商管理中的 "现金余额(积分值)",仅支持区块链方式充值与提现: - 100亿销毁池 (wallet-22fd661f, 2-of-3 门限) 地址: 0xdE2932D2A25e1698c1354A41e2e46B414C46F5a1 - 200万挖矿池 (wallet-974e78f5, 2-of-3 门限) 地址: 0x8BC9091375ae8ef43ae011F0f9bAf10e51bC9D59 具体改动: - .env.production: 新增 BURN_POOL / MINING_POOL 钱包名和地址环境变量 - configs.api.ts: 新增 tradingClient、PoolAccountBalance 接口、 getPoolAccountBalance 和 poolAccountBlockchainWithdraw API - use-configs.ts: 新增 usePoolAccountBalance (30s 刷新) 和 usePoolAccountBlockchainWithdraw hooks - configs/page.tsx: 新增两个并排池账户 Card,包含余额展示、 QR 码充值弹窗、区块链提现弹窗(含 0x 地址校验) 后端需实现: - GET /admin/pool-accounts/:walletName/balance - POST /admin/pool-accounts/:walletName/blockchain-withdraw Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
999d0389b3
commit
6dbb620e82
|
|
@ -1,3 +1,11 @@
|
||||||
NEXT_PUBLIC_API_URL=https://mapi.szaiai.com/api/v2/mining-admin
|
NEXT_PUBLIC_API_URL=https://mapi.szaiai.com/api/v2/mining-admin
|
||||||
TRADING_SERVICE_URL=https://mapi.szaiai.com/api/v2/trading
|
TRADING_SERVICE_URL=https://mapi.szaiai.com/api/v2/trading
|
||||||
NEXT_PUBLIC_APP_NAME=挖矿管理后台
|
NEXT_PUBLIC_APP_NAME=挖矿管理后台
|
||||||
|
|
||||||
|
# 100亿销毁池钱包配置
|
||||||
|
NEXT_PUBLIC_BURN_POOL_WALLET_NAME=wallet-22fd661f
|
||||||
|
NEXT_PUBLIC_BURN_POOL_WALLET_ADDRESS=0xdE2932D2A25e1698c1354A41e2e46B414C46F5a1
|
||||||
|
|
||||||
|
# 200万挖矿池钱包配置
|
||||||
|
NEXT_PUBLIC_MINING_POOL_WALLET_NAME=wallet-974e78f5
|
||||||
|
NEXT_PUBLIC_MINING_POOL_WALLET_ADDRESS=0x8BC9091375ae8ef43ae011F0f9bAf10e51bC9D59
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,34 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from '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 } from '@/features/configs/hooks/use-configs';
|
import { useConfigs, useUpdateConfig, useTransferEnabled, useSetTransferEnabled, useMiningStatus, useActivateMining, useDeactivateMining, useP2pTransferFee, useSetP2pTransferFee, usePoolAccountBalance, usePoolAccountBlockchainWithdraw } from '@/features/configs/hooks/use-configs';
|
||||||
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 { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Pencil, Save, X, Play, Pause, AlertCircle, CheckCircle2, Loader2 } 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';
|
||||||
|
|
||||||
|
const BURN_POOL_WALLET_NAME = process.env.NEXT_PUBLIC_BURN_POOL_WALLET_NAME || 'wallet-22fd661f';
|
||||||
|
const BURN_POOL_WALLET_ADDRESS = process.env.NEXT_PUBLIC_BURN_POOL_WALLET_ADDRESS || '0xdE2932D2A25e1698c1354A41e2e46B414C46F5a1';
|
||||||
|
const MINING_POOL_WALLET_NAME = process.env.NEXT_PUBLIC_MINING_POOL_WALLET_NAME || 'wallet-974e78f5';
|
||||||
|
const MINING_POOL_WALLET_ADDRESS = process.env.NEXT_PUBLIC_MINING_POOL_WALLET_ADDRESS || '0x8BC9091375ae8ef43ae011F0f9bAf10e51bC9D59';
|
||||||
|
|
||||||
const categoryLabels: Record<string, string> = {
|
const categoryLabels: Record<string, string> = {
|
||||||
mining: '挖矿配置',
|
mining: '挖矿配置',
|
||||||
trading: '交易配置',
|
trading: '交易配置',
|
||||||
|
|
@ -34,11 +48,22 @@ export default function ConfigsPage() {
|
||||||
const { data: feeConfig, isLoading: feeLoading } = useP2pTransferFee();
|
const { data: feeConfig, isLoading: feeLoading } = useP2pTransferFee();
|
||||||
const setP2pTransferFee = useSetP2pTransferFee();
|
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 [editingConfig, setEditingConfig] = useState<SystemConfig | null>(null);
|
const [editingConfig, setEditingConfig] = useState<SystemConfig | null>(null);
|
||||||
const [editValue, setEditValue] = useState('');
|
const [editValue, setEditValue] = useState('');
|
||||||
const [feeValue, setFeeValue] = useState('');
|
const [feeValue, setFeeValue] = useState('');
|
||||||
const [minAmountValue, setMinAmountValue] = useState('');
|
const [minAmountValue, setMinAmountValue] = useState('');
|
||||||
|
|
||||||
|
// 池账户区块链提现状态
|
||||||
|
const [burnWithdrawAddress, setBurnWithdrawAddress] = useState('');
|
||||||
|
const [burnWithdrawAmount, setBurnWithdrawAmount] = useState('');
|
||||||
|
const [miningWithdrawAddress, setMiningWithdrawAddress] = useState('');
|
||||||
|
const [miningWithdrawAmount, setMiningWithdrawAmount] = useState('');
|
||||||
|
const [copiedAddress, setCopiedAddress] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (feeConfig) {
|
if (feeConfig) {
|
||||||
setFeeValue(feeConfig.fee);
|
setFeeValue(feeConfig.fee);
|
||||||
|
|
@ -46,6 +71,19 @@ export default function ConfigsPage() {
|
||||||
}
|
}
|
||||||
}, [feeConfig]);
|
}, [feeConfig]);
|
||||||
|
|
||||||
|
const handleCopyAddress = async (address: string) => {
|
||||||
|
await navigator.clipboard.writeText(address);
|
||||||
|
setCopiedAddress(address);
|
||||||
|
setTimeout(() => setCopiedAddress(null), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBalance = (value: string | undefined, decimals: number = 2) => {
|
||||||
|
if (!value) return '0';
|
||||||
|
const num = parseFloat(value);
|
||||||
|
if (isNaN(num)) return '0';
|
||||||
|
return num.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
||||||
|
};
|
||||||
|
|
||||||
const handleEdit = (config: SystemConfig) => {
|
const handleEdit = (config: SystemConfig) => {
|
||||||
setEditingConfig(config);
|
setEditingConfig(config);
|
||||||
setEditValue(config.configValue);
|
setEditValue(config.configValue);
|
||||||
|
|
@ -194,6 +232,291 @@ export default function ConfigsPage() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* 池账户管理 */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{/* 100亿销毁池 */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
<Flame className="h-5 w-5 text-orange-500" />
|
||||||
|
100亿销毁池
|
||||||
|
</CardTitle>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
2-of-3 门限
|
||||||
|
</Badge>
|
||||||
|
</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>向以下地址转入 fUSDT(积分值代币)</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<div className="p-4 bg-white rounded-lg">
|
||||||
|
<QRCodeSVG value={BURN_POOL_WALLET_ADDRESS} 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">
|
||||||
|
{BURN_POOL_WALLET_ADDRESS}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleCopyAddress(BURN_POOL_WALLET_ADDRESS)}
|
||||||
|
>
|
||||||
|
{copiedAddress === BURN_POOL_WALLET_ADDRESS ? <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>
|
||||||
|
</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>向以下地址转入 fUSDT(积分值代币)</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<div className="p-4 bg-white rounded-lg">
|
||||||
|
<QRCodeSVG value={MINING_POOL_WALLET_ADDRESS} 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">
|
||||||
|
{MINING_POOL_WALLET_ADDRESS}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleCopyAddress(MINING_POOL_WALLET_ADDRESS)}
|
||||||
|
>
|
||||||
|
{copiedAddress === MINING_POOL_WALLET_ADDRESS ? <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>
|
||||||
|
</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>
|
||||||
<CardTitle className="text-lg">划转开关</CardTitle>
|
<CardTitle className="text-lg">划转开关</CardTitle>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,62 @@
|
||||||
import { apiClient } from '@/lib/api/client';
|
import { apiClient } from '@/lib/api/client';
|
||||||
|
import axios from 'axios';
|
||||||
import type { SystemConfig } from '@/types/config';
|
import type { SystemConfig } from '@/types/config';
|
||||||
|
|
||||||
|
const tradingBaseURL = '/api/trading';
|
||||||
|
|
||||||
|
const tradingClient = axios.create({
|
||||||
|
baseURL: tradingBaseURL,
|
||||||
|
timeout: 30000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
tradingClient.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = typeof window !== 'undefined' ? localStorage.getItem('admin_token') : null;
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error)
|
||||||
|
);
|
||||||
|
|
||||||
|
tradingClient.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
if (response.data && response.data.data !== undefined) {
|
||||||
|
response.data = response.data.data;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
localStorage.removeItem('admin_token');
|
||||||
|
if (typeof window !== 'undefined' && !window.location.pathname.includes('/login')) {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export interface ContributionSyncStatus {
|
export interface ContributionSyncStatus {
|
||||||
isSynced: boolean;
|
isSynced: boolean;
|
||||||
miningNetworkTotal: string;
|
miningNetworkTotal: string;
|
||||||
networkTotalContribution: string;
|
networkTotalContribution: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PoolAccountBalance {
|
||||||
|
walletName: string;
|
||||||
|
walletAddress: string;
|
||||||
|
balance: string;
|
||||||
|
availableBalance: string;
|
||||||
|
frozenBalance: string;
|
||||||
|
threshold: string;
|
||||||
|
lastUpdated?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MiningStatus {
|
export interface MiningStatus {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
@ -63,4 +113,23 @@ export const configsApi = {
|
||||||
setP2pTransferFee: async (fee: string, minTransferAmount: string): Promise<void> => {
|
setP2pTransferFee: async (fee: string, minTransferAmount: string): Promise<void> => {
|
||||||
await apiClient.post('/configs/p2p-transfer-fee', { fee, minTransferAmount });
|
await apiClient.post('/configs/p2p-transfer-fee', { fee, minTransferAmount });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 获取池账户余额
|
||||||
|
getPoolAccountBalance: async (walletName: string): Promise<PoolAccountBalance> => {
|
||||||
|
const response = await tradingClient.get(`/admin/pool-accounts/${walletName}/balance`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 区块链提现(从池账户)
|
||||||
|
poolAccountBlockchainWithdraw: async (walletName: string, toAddress: string, amount: string): Promise<{
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
txHash?: string;
|
||||||
|
blockNumber?: number;
|
||||||
|
newBalance?: string;
|
||||||
|
error?: string;
|
||||||
|
}> => {
|
||||||
|
const response = await tradingClient.post(`/admin/pool-accounts/${walletName}/blockchain-withdraw`, { toAddress, amount });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { configsApi } from '../api/configs.api';
|
import { configsApi } from '../api/configs.api';
|
||||||
import { useToast } from '@/lib/hooks/use-toast';
|
import { useToast } from '@/lib/hooks/use-toast';
|
||||||
|
import { toast } from '@/lib/hooks/use-toast';
|
||||||
|
|
||||||
export function useConfigs() {
|
export function useConfigs() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|
@ -119,3 +120,34 @@ export function useSetP2pTransferFee() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePoolAccountBalance(walletName: string) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['configs', 'pool-account', walletName],
|
||||||
|
queryFn: () => configsApi.getPoolAccountBalance(walletName),
|
||||||
|
refetchInterval: 30000,
|
||||||
|
enabled: !!walletName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePoolAccountBlockchainWithdraw() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({ walletName, toAddress, amount }: { walletName: string; toAddress: string; amount: string }) =>
|
||||||
|
configsApi.poolAccountBlockchainWithdraw(walletName, toAddress, amount),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
if (data.success) {
|
||||||
|
toast({
|
||||||
|
title: '区块链提现成功',
|
||||||
|
description: `交易哈希: ${data.txHash?.slice(0, 20)}...`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({ title: '提现失败', description: data.error || data.message, variant: 'destructive' });
|
||||||
|
}
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['configs', 'pool-account'] });
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast({ title: '错误', description: error.response?.data?.message || '区块链提现失败', variant: 'destructive' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue