feat(admin): refactor admin console to focus on superadmin tier, graduation and dispute governance
This commit is contained in:
@@ -3,13 +3,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Web3Provider } from '../context/Web3Context';
|
import { Web3Provider } from '../context/Web3Context';
|
||||||
import { AdminNavbar } from '../features/common/AdminNavbar';
|
import { AdminNavbar } from '../features/common/AdminNavbar';
|
||||||
import { FaucetPanel } from '../features/faucet/FaucetPanel';
|
|
||||||
import { MarketWizard } from '../features/wizard/MarketWizard';
|
|
||||||
import { GovernancePanel } from '../features/governance/GovernancePanel';
|
import { GovernancePanel } from '../features/governance/GovernancePanel';
|
||||||
import { AdjudicationPanel } from '../features/adjudication/AdjudicationPanel';
|
import { AdjudicationPanel } from '../features/adjudication/AdjudicationPanel';
|
||||||
|
import { FaucetPanel } from '../features/faucet/FaucetPanel';
|
||||||
|
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const [activeTab, setActiveTab] = useState<string>('faucet');
|
const [activeTab, setActiveTab] = useState<string>('governance');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Web3Provider>
|
<Web3Provider>
|
||||||
@@ -17,14 +16,13 @@ export default function AdminPage() {
|
|||||||
<AdminNavbar activeTab={activeTab} setActiveTab={setActiveTab} />
|
<AdminNavbar activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||||
|
|
||||||
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{activeTab === 'faucet' && <FaucetPanel />}
|
|
||||||
{activeTab === 'wizard' && <MarketWizard />}
|
|
||||||
{activeTab === 'governance' && <GovernancePanel />}
|
{activeTab === 'governance' && <GovernancePanel />}
|
||||||
{activeTab === 'adjudication' && <AdjudicationPanel />}
|
{activeTab === 'adjudication' && <AdjudicationPanel />}
|
||||||
|
{activeTab === 'faucet' && <FaucetPanel />}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer className="border-t border-slate-900 py-6 text-center text-xs text-slate-600">
|
<footer className="border-t border-slate-900 py-6 text-center text-xs text-slate-600">
|
||||||
WTFX Protocol Governance & Administration Console • Robinhood Chain Testnet (ID: 46630)
|
WTFX Protocol SuperAdmin Console • Robinhood Chain Testnet (ID: 46630)
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</Web3Provider>
|
</Web3Provider>
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ export const AdminNavbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab })
|
|||||||
const { account, isCorrectNetwork, connectWallet, switchNetwork } = useWeb3();
|
const { account, isCorrectNetwork, connectWallet, switchNetwork } = useWeb3();
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ id: 'faucet', label: '🚰 WUSD 水龙头' },
|
{ id: 'governance', label: '⚙️ 协议全局治理' },
|
||||||
{ id: 'wizard', label: '🚀 创建市场向导' },
|
{ id: 'adjudication', label: '⚖️ 市场争议终裁与熔断' },
|
||||||
{ id: 'governance', label: '⚙️ 协议参数治理' },
|
{ id: 'faucet', label: '🚰 超管铸币水龙头' },
|
||||||
{ id: 'adjudication', label: '⚖️ 市场风控与裁决' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useWeb3 } from '../../context/Web3Context';
|
import { useWeb3 } from '../../context/Web3Context';
|
||||||
import { DEPLOYMENTS } from '@wtfx/contracts';
|
import { DEPLOYMENTS } from '@wtfx/contracts';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import { Sliders, ShieldCheck, Percent, Wallet, Check, AlertCircle, Loader2, RefreshCw } from 'lucide-react';
|
import { Sliders, ShieldCheck, Percent, Wallet, Check, AlertCircle, Loader2, RefreshCw, Layers, Award, Clock } from 'lucide-react';
|
||||||
|
|
||||||
export const GovernancePanel: React.FC = () => {
|
export const GovernancePanel: React.FC = () => {
|
||||||
const { account, signer, getControllerContract, isCorrectNetwork } = useWeb3();
|
const { account, signer, getControllerContract, isCorrectNetwork } = useWeb3();
|
||||||
@@ -11,16 +11,20 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
const [defaultFeeRate, setDefaultFeeRate] = useState<string>('0.6');
|
const [defaultFeeRate, setDefaultFeeRate] = useState<string>('0.6');
|
||||||
const [creatorShare, setCreatorShare] = useState<string>('50');
|
const [creatorShare, setCreatorShare] = useState<string>('50');
|
||||||
const [centralWallet, setCentralWallet] = useState<string>('');
|
const [centralWallet, setCentralWallet] = useState<string>('');
|
||||||
|
|
||||||
|
// Tier 与 毕业阈值 & 争议窗口
|
||||||
|
const [selectedTier, setSelectedTier] = useState<number>(1);
|
||||||
|
const [tierStartAmount, setTierStartAmount] = useState<string>('1.1');
|
||||||
|
const [tierPower, setTierPower] = useState<string>('1.0');
|
||||||
|
const [graduationPoolCap, setGraduationPoolCap] = useState<string>('100000');
|
||||||
|
const [graduationMaxSupply, setGraduationMaxSupply] = useState<string>('1000000');
|
||||||
|
const [disputeWindowDays, setDisputeWindowDays] = useState<string>('1');
|
||||||
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [statusMsg, setStatusMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
const [statusMsg, setStatusMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
const fetchGovParams = async () => {
|
const fetchGovParams = async () => {
|
||||||
try {
|
try {
|
||||||
const controller = getControllerContract();
|
|
||||||
if (!controller) return;
|
|
||||||
|
|
||||||
// In real scenario, read from getters or view functions
|
|
||||||
// We fill initialized defaults from deployment
|
|
||||||
setTreasury(DEPLOYMENTS.robinhoodTestnet.governance.treasury);
|
setTreasury(DEPLOYMENTS.robinhoodTestnet.governance.treasury);
|
||||||
setCentralWallet(DEPLOYMENTS.robinhoodTestnet.governance.admin);
|
setCentralWallet(DEPLOYMENTS.robinhoodTestnet.governance.admin);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -51,7 +55,7 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateFeeRate = async () => {
|
const handleUpdateTierConfig = async () => {
|
||||||
if (!signer || !account) return;
|
if (!signer || !account) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -59,11 +63,12 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
const controller = getControllerContract();
|
const controller = getControllerContract();
|
||||||
if (!controller) throw new Error('Controller not found');
|
if (!controller) throw new Error('Controller not found');
|
||||||
|
|
||||||
const rateWei = ethers.parseUnits((Number(defaultFeeRate) / 100).toString(), 18);
|
const startWei = ethers.parseUnits(tierStartAmount, 18);
|
||||||
const tx = await controller.setFeeRateDefault(rateWei);
|
const powerWei = ethers.parseUnits(tierPower, 18);
|
||||||
setStatusMsg({ type: 'success', text: `更新默认手续费交易已广播: ${tx.hash.slice(0, 10)}...` });
|
const tx = await controller.setMarketTier(selectedTier, startWei, powerWei);
|
||||||
|
setStatusMsg({ type: 'success', text: `更新 Tier ${selectedTier} 曲线参数交易已广播: ${tx.hash.slice(0, 10)}...` });
|
||||||
await tx.wait();
|
await tx.wait();
|
||||||
setStatusMsg({ type: 'success', text: `🎉 全局默认协议手续费率已成功更新为: ${defaultFeeRate}%` });
|
setStatusMsg({ type: 'success', text: `🎉 Tier ${selectedTier} 标准参数已成功更新 (Start: ${tierStartAmount}, Power: ${tierPower})!` });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setStatusMsg({ type: 'error', text: err.reason || err.message || '更新失败' });
|
setStatusMsg({ type: 'error', text: err.reason || err.message || '更新失败' });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -71,7 +76,7 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateCreatorShare = async () => {
|
const handleUpdateGraduation = async () => {
|
||||||
if (!signer || !account) return;
|
if (!signer || !account) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -79,11 +84,12 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
const controller = getControllerContract();
|
const controller = getControllerContract();
|
||||||
if (!controller) throw new Error('Controller not found');
|
if (!controller) throw new Error('Controller not found');
|
||||||
|
|
||||||
const shareWei = ethers.parseUnits((Number(creatorShare) / 100).toString(), 18);
|
const capWei = ethers.parseUnits(graduationPoolCap, 18);
|
||||||
const tx = await controller.setCreatorShare(shareWei);
|
const supplyWei = ethers.parseUnits(graduationMaxSupply, 18);
|
||||||
setStatusMsg({ type: 'success', text: `更新建盘者手续费分成交易已广播: ${tx.hash.slice(0, 10)}...` });
|
const tx = await controller.setGraduationThresholds(capWei, supplyWei);
|
||||||
|
setStatusMsg({ type: 'success', text: `更新毕业资金池阈值交易已广播: ${tx.hash.slice(0, 10)}...` });
|
||||||
await tx.wait();
|
await tx.wait();
|
||||||
setStatusMsg({ type: 'success', text: `🎉 建盘者手续费返佣分成比例已成功更新为: ${creatorShare}%` });
|
setStatusMsg({ type: 'success', text: `🎉 市场毕业门槛已更新:资金池达到 ${Number(graduationPoolCap).toLocaleString()} WUSD 自动毕业!` });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setStatusMsg({ type: 'error', text: err.reason || err.message || '更新失败' });
|
setStatusMsg({ type: 'error', text: err.reason || err.message || '更新失败' });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -91,7 +97,7 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateCentralWallet = async () => {
|
const handleUpdateDisputeWindow = async () => {
|
||||||
if (!signer || !account) return;
|
if (!signer || !account) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -99,10 +105,11 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
const controller = getControllerContract();
|
const controller = getControllerContract();
|
||||||
if (!controller) throw new Error('Controller not found');
|
if (!controller) throw new Error('Controller not found');
|
||||||
|
|
||||||
const tx = await controller.setCentralWallet(centralWallet.trim());
|
const windowSec = Math.floor(Number(disputeWindowDays) * 86400);
|
||||||
setStatusMsg({ type: 'success', text: `更新中央安全提币钱包交易已广播: ${tx.hash.slice(0, 10)}...` });
|
const tx = await controller.setDisputeWindow(windowSec);
|
||||||
|
setStatusMsg({ type: 'success', text: `更新争议公示期窗口交易已广播: ${tx.hash.slice(0, 10)}...` });
|
||||||
await tx.wait();
|
await tx.wait();
|
||||||
setStatusMsg({ type: 'success', text: `🎉 中央安全提币钱包已成功更新为: ${centralWallet}` });
|
setStatusMsg({ type: 'success', text: `🎉 市场初裁后的争议公示期已成功更新为: ${disputeWindowDays} 天!` });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setStatusMsg({ type: 'error', text: err.reason || err.message || '更新失败' });
|
setStatusMsg({ type: 'error', text: err.reason || err.message || '更新失败' });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -112,15 +119,15 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto space-y-6">
|
<div className="max-w-4xl mx-auto space-y-6">
|
||||||
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl space-y-6">
|
<div className="bg-slate-900 border border-slate-800 rounded-3xl p-6 sm:p-8 shadow-xl space-y-6">
|
||||||
<div className="border-b border-slate-800 pb-5 flex items-center justify-between">
|
<div className="border-b border-slate-800 pb-5 flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="p-3 bg-amber-500/10 rounded-xl border border-amber-500/20 text-amber-400">
|
<div className="p-3 bg-amber-500/10 rounded-2xl border border-amber-500/20 text-amber-400">
|
||||||
<Sliders className="w-6 h-6" />
|
<Sliders className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-white">协议治理看板 (Protocol Governance)</h2>
|
<h2 className="text-xl font-bold text-white">协议全局治理控制台 (SuperAdmin Protocol Governance)</h2>
|
||||||
<p className="text-sm text-slate-400 mt-0.5">
|
<p className="text-xs text-slate-400 mt-0.5">
|
||||||
WTFControllerV2 智能合约超管参数配置(仅 DEFAULT_ADMIN_ROLE / OPERATOR_ROLE 可执行)
|
WTFControllerV2 智能合约超管参数配置(仅 DEFAULT_ADMIN_ROLE / OPERATOR_ROLE 可执行)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,100 +135,135 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={fetchGovParams}
|
onClick={fetchGovParams}
|
||||||
className="p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded-lg transition-colors"
|
className="p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded-xl transition-colors"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-4 h-4" />
|
<RefreshCw className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
{/* 1. Tier 治理档位配置 */}
|
||||||
{/* Treasury Setting */}
|
<div className="bg-slate-950/60 border border-slate-800 rounded-2xl p-5 space-y-4">
|
||||||
<div className="bg-slate-950/50 border border-slate-800 rounded-xl p-4 space-y-3">
|
<div className="flex items-center space-x-2 text-sm font-bold text-white">
|
||||||
<div className="flex items-center space-x-2 text-sm font-semibold text-slate-200">
|
<Layers className="w-4 h-4 text-cyan-400" />
|
||||||
<ShieldCheck className="w-4 h-4 text-cyan-400" />
|
<span>1. 市场治理分级参数 (Market Tier Curves Config)</span>
|
||||||
<span>协议国库金库 (Treasury Address)</span>
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">设置不同 Tier 市场启动时的 Power LDA 曲线底仓幂律参数。</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
|
{[1, 2, 3].map((t) => (
|
||||||
|
<button
|
||||||
|
key={t}
|
||||||
|
onClick={() => setSelectedTier(t)}
|
||||||
|
className={`py-2 text-xs font-bold rounded-xl border transition-all ${
|
||||||
|
selectedTier === t
|
||||||
|
? 'bg-cyan-950/60 border-cyan-500 text-cyan-300'
|
||||||
|
: 'bg-slate-900 border-slate-800 text-slate-400 hover:border-slate-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Tier {t}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-slate-400 mb-1">底仓起点参数 (Start - 须 >= 1.1)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
value={tierStartAmount}
|
||||||
|
onChange={(e) => setTierStartAmount(e.target.value)}
|
||||||
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-slate-400">用于接收协议留存手续费分润的冷钱包/金库合约。</p>
|
<div>
|
||||||
|
<label className="block text-xs font-medium text-slate-400 mb-1">曲率指数参数 (Power - 默认 1.0)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
value={tierPower}
|
||||||
|
onChange={(e) => setTierPower(e.target.value)}
|
||||||
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleUpdateTierConfig}
|
||||||
|
disabled={loading || !account}
|
||||||
|
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-xl text-xs font-bold transition-colors"
|
||||||
|
>
|
||||||
|
更新 Tier {selectedTier} 参数
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2. 毕业阈值与争议公示期 */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="bg-slate-950/60 border border-slate-800 rounded-2xl p-5 space-y-3">
|
||||||
|
<div className="flex items-center space-x-2 text-sm font-bold text-white">
|
||||||
|
<Award className="w-4 h-4 text-amber-400" />
|
||||||
|
<span>2. 市场毕业门槛 (Graduation Pool Cap)</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">当市场累积保证金池达到该阈值时自动毕业锁定。</p>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={graduationPoolCap}
|
||||||
|
onChange={(e) => setGraduationPoolCap(e.target.value)}
|
||||||
|
placeholder="100000"
|
||||||
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleUpdateGraduation}
|
||||||
|
disabled={loading || !account}
|
||||||
|
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-xl text-xs font-bold transition-colors"
|
||||||
|
>
|
||||||
|
更新毕业资金池门槛
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-slate-950/60 border border-slate-800 rounded-2xl p-5 space-y-3">
|
||||||
|
<div className="flex items-center space-x-2 text-sm font-bold text-white">
|
||||||
|
<Clock className="w-4 h-4 text-indigo-400" />
|
||||||
|
<span>3. 争议公示期窗口 (Dispute Window)</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">创建者初裁后允许社区提出异议的等待天数。</p>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={disputeWindowDays}
|
||||||
|
onChange={(e) => setDisputeWindowDays(e.target.value)}
|
||||||
|
placeholder="1"
|
||||||
|
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleUpdateDisputeWindow}
|
||||||
|
disabled={loading || !account}
|
||||||
|
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-xl text-xs font-bold transition-colors"
|
||||||
|
>
|
||||||
|
更新争议窗口 (天)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3. 国库与安全地址 */}
|
||||||
|
<div className="bg-slate-950/60 border border-slate-800 rounded-2xl p-5 space-y-3">
|
||||||
|
<div className="flex items-center space-x-2 text-sm font-bold text-white">
|
||||||
|
<ShieldCheck className="w-4 h-4 text-emerald-400" />
|
||||||
|
<span>4. 协议国库金库地址 (Treasury Address)</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-400">接收协议留存 50% 手续费分润的冷钱包/金库合约。</p>
|
||||||
|
<div className="flex space-x-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={treasury}
|
value={treasury}
|
||||||
onChange={(e) => setTreasury(e.target.value)}
|
onChange={(e) => setTreasury(e.target.value)}
|
||||||
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
className="flex-1 bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleUpdateTreasury}
|
onClick={handleUpdateTreasury}
|
||||||
disabled={loading || !account}
|
disabled={loading || !account}
|
||||||
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-lg text-xs font-medium transition-colors"
|
className="px-5 py-2 bg-cyan-600 hover:bg-cyan-500 text-white rounded-lg text-xs font-bold transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
更新国库地址
|
更新国库
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Central Wallet */}
|
|
||||||
<div className="bg-slate-950/50 border border-slate-800 rounded-xl p-4 space-y-3">
|
|
||||||
<div className="flex items-center space-x-2 text-sm font-semibold text-slate-200">
|
|
||||||
<Wallet className="w-4 h-4 text-indigo-400" />
|
|
||||||
<span>中央安全提币钱包 (Central Wallet)</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-slate-400">由超管授权以自动化划拨协议手续费的专用钱包。</p>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={centralWallet}
|
|
||||||
onChange={(e) => setCentralWallet(e.target.value)}
|
|
||||||
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleUpdateCentralWallet}
|
|
||||||
disabled={loading || !account}
|
|
||||||
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-lg text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
更新中央钱包
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Default Fee Rate */}
|
|
||||||
<div className="bg-slate-950/50 border border-slate-800 rounded-xl p-4 space-y-3">
|
|
||||||
<div className="flex items-center space-x-2 text-sm font-semibold text-slate-200">
|
|
||||||
<Percent className="w-4 h-4 text-emerald-400" />
|
|
||||||
<span>默认协议手续费率 (Fee Rate %)</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-slate-400">新建市场默认费率(范围 [0.1%, 3.0%],默认 0.6%)。</p>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
value={defaultFeeRate}
|
|
||||||
onChange={(e) => setDefaultFeeRate(e.target.value)}
|
|
||||||
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleUpdateFeeRate}
|
|
||||||
disabled={loading || !account}
|
|
||||||
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-lg text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
更新默认手续费
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Creator Share */}
|
|
||||||
<div className="bg-slate-950/50 border border-slate-800 rounded-xl p-4 space-y-3">
|
|
||||||
<div className="flex items-center space-x-2 text-sm font-semibold text-slate-200">
|
|
||||||
<Percent className="w-4 h-4 text-pink-400" />
|
|
||||||
<span>建盘者返佣分成比例 (Creator Share %)</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-slate-400">交易产生的手续费中分给市场创建者的比例(默认 50%)。</p>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="1"
|
|
||||||
value={creatorShare}
|
|
||||||
onChange={(e) => setCreatorShare(e.target.value)}
|
|
||||||
className="w-full bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleUpdateCreatorShare}
|
|
||||||
disabled={loading || !account}
|
|
||||||
className="w-full py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-600 rounded-lg text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
更新建盘者分成
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -229,16 +271,16 @@ export const GovernancePanel: React.FC = () => {
|
|||||||
{/* Status Message */}
|
{/* Status Message */}
|
||||||
{statusMsg && (
|
{statusMsg && (
|
||||||
<div
|
<div
|
||||||
className={`p-4 rounded-xl border flex items-start space-x-3 text-sm ${
|
className={`p-4 rounded-2xl border flex items-start space-x-3 text-xs ${
|
||||||
statusMsg.type === 'success'
|
statusMsg.type === 'success'
|
||||||
? 'bg-emerald-950/30 border-emerald-800/50 text-emerald-300'
|
? 'bg-emerald-950/30 border-emerald-800/50 text-emerald-300'
|
||||||
: 'bg-rose-950/30 border-rose-800/50 text-rose-300'
|
: 'bg-rose-950/30 border-rose-800/50 text-rose-300'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{statusMsg.type === 'success' ? (
|
{statusMsg.type === 'success' ? (
|
||||||
<Check className="w-5 h-5 flex-shrink-0 text-emerald-400" />
|
<Check className="w-4 h-4 flex-shrink-0 text-emerald-400" />
|
||||||
) : (
|
) : (
|
||||||
<AlertCircle className="w-5 h-5 flex-shrink-0 text-rose-400" />
|
<AlertCircle className="w-4 h-4 flex-shrink-0 text-rose-400" />
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 overflow-hidden break-words">
|
<div className="flex-1 overflow-hidden break-words">
|
||||||
<p>{statusMsg.text}</p>
|
<p>{statusMsg.text}</p>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { WebNavbar } from '../components/WebNavbar';
|
|||||||
import { MarketExplore, MarketItem } from '../features/explore/MarketExplore';
|
import { MarketExplore, MarketItem } from '../features/explore/MarketExplore';
|
||||||
import { TradingTerminal } from '../features/trading/TradingTerminal';
|
import { TradingTerminal } from '../features/trading/TradingTerminal';
|
||||||
import { PortfolioView } from '../features/portfolio/PortfolioView';
|
import { PortfolioView } from '../features/portfolio/PortfolioView';
|
||||||
|
import { UserMarketWizard } from '../features/create/UserMarketWizard';
|
||||||
|
|
||||||
const DEFAULT_MARKET: MarketItem = {
|
const DEFAULT_MARKET: MarketItem = {
|
||||||
id: 'm-1',
|
id: 'm-1',
|
||||||
@@ -40,6 +41,7 @@ export default function WebApp() {
|
|||||||
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<main className="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{activeTab === 'explore' && <MarketExplore onSelectMarket={handleSelectMarket} />}
|
{activeTab === 'explore' && <MarketExplore onSelectMarket={handleSelectMarket} />}
|
||||||
{activeTab === 'trade' && <TradingTerminal market={selectedMarket} />}
|
{activeTab === 'trade' && <TradingTerminal market={selectedMarket} />}
|
||||||
|
{activeTab === 'create' && <UserMarketWizard />}
|
||||||
{activeTab === 'portfolio' && <PortfolioView />}
|
{activeTab === 'portfolio' && <PortfolioView />}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useWeb3 } from '../context/Web3Context';
|
import { useWeb3 } from '../context/Web3Context';
|
||||||
import { Wallet, TrendingUp, Compass, PieChart, ShieldAlert, CheckCircle2 } from 'lucide-react';
|
import { Wallet, TrendingUp, Compass, PieChart, PlusCircle, Droplet, ShieldAlert } from 'lucide-react';
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
@@ -8,12 +8,31 @@ interface NavbarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const WebNavbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab }) => {
|
export const WebNavbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab }) => {
|
||||||
const { account, wusdBalance, isCorrectNetwork, connectWallet, switchNetwork } = useWeb3();
|
const { account, wusdBalance, isCorrectNetwork, connectWallet, switchNetwork, refreshBalance, getCollateralContract } = useWeb3();
|
||||||
|
const [faucetLoading, setFaucetLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleQuickFaucet = async () => {
|
||||||
|
if (!account) return;
|
||||||
|
try {
|
||||||
|
setFaucetLoading(true);
|
||||||
|
const collateral = getCollateralContract();
|
||||||
|
if (!collateral) return;
|
||||||
|
const tx = await collateral.mint(account, "100000000000000000000000000"); // 100M WUSD
|
||||||
|
await tx.wait();
|
||||||
|
refreshBalance();
|
||||||
|
alert('🎉 成功领取 100,000,000 WUSD 测试金!');
|
||||||
|
} catch (e: any) {
|
||||||
|
alert('领水失败: ' + (e.message || e));
|
||||||
|
} finally {
|
||||||
|
setFaucetLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const navs = [
|
const navs = [
|
||||||
{ id: 'explore', label: '🔥 市场探索', icon: Compass },
|
{ id: 'explore', label: '🔥 市场探索', icon: Compass },
|
||||||
{ id: 'trade', label: '⚡ 交易终端', icon: TrendingUp },
|
{ id: 'trade', label: '⚡ 交易终端', icon: TrendingUp },
|
||||||
{ id: 'portfolio', label: '💼 我的持仓与账本', icon: PieChart },
|
{ id: 'create', label: '🚀 创建市场', icon: PlusCircle },
|
||||||
|
{ id: 'portfolio', label: '💼 持仓与金库', icon: PieChart },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,7 +49,7 @@ export const WebNavbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab }) =>
|
|||||||
WTFX
|
WTFX
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] font-mono font-bold px-1.5 py-0.5 rounded bg-gradient-to-r from-cyan-500/20 to-purple-500/20 text-cyan-300 border border-cyan-500/30">
|
<span className="text-[10px] font-mono font-bold px-1.5 py-0.5 rounded bg-gradient-to-r from-cyan-500/20 to-purple-500/20 text-cyan-300 border border-cyan-500/30">
|
||||||
POWER LDA
|
TESTNET
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +62,7 @@ export const WebNavbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab }) =>
|
|||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => setActiveTab(item.id)}
|
onClick={() => setActiveTab(item.id)}
|
||||||
className={`flex items-center space-x-2 px-4 py-2 rounded-xl text-sm font-semibold transition-all ${
|
className={`flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs sm:text-sm font-semibold transition-all ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-slate-800/80 text-white shadow-sm border border-slate-700/80'
|
? 'bg-slate-800/80 text-white shadow-sm border border-slate-700/80'
|
||||||
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/40'
|
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/40'
|
||||||
@@ -57,17 +76,28 @@ export const WebNavbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab }) =>
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Section / Wallet Status */}
|
{/* Right Section / Faucet & Wallet */}
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-3">
|
||||||
|
{account && (
|
||||||
|
<button
|
||||||
|
onClick={handleQuickFaucet}
|
||||||
|
disabled={faucetLoading}
|
||||||
|
className="flex items-center space-x-1.5 px-3 py-1.5 bg-cyan-950/60 hover:bg-cyan-900/60 text-cyan-400 border border-cyan-800/50 rounded-xl text-xs font-bold transition-colors"
|
||||||
|
>
|
||||||
|
<Droplet className="w-3.5 h-3.5" />
|
||||||
|
<span>{faucetLoading ? '领水中...' : '全民领水 1 亿'}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{account ? (
|
{account ? (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
{!isCorrectNetwork ? (
|
{!isCorrectNetwork ? (
|
||||||
<button
|
<button
|
||||||
onClick={switchNetwork}
|
onClick={switchNetwork}
|
||||||
className="flex items-center space-x-1.5 bg-amber-500/10 text-amber-400 border border-amber-500/30 px-3 py-1.5 rounded-lg text-xs font-medium hover:bg-amber-500/20 transition-colors"
|
className="flex items-center space-x-1.5 bg-amber-500/10 text-amber-400 border border-amber-500/30 px-3 py-1.5 rounded-lg text-xs font-medium hover:bg-amber-500/20"
|
||||||
>
|
>
|
||||||
<ShieldAlert className="w-4 h-4" />
|
<ShieldAlert className="w-4 h-4" />
|
||||||
<span>切换 Robinhood 测试网</span>
|
<span>切换测试网</span>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="hidden sm:flex items-center space-x-2 bg-slate-900/80 border border-slate-800 px-3 py-1.5 rounded-xl">
|
<div className="hidden sm:flex items-center space-x-2 bg-slate-900/80 border border-slate-800 px-3 py-1.5 rounded-xl">
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useWeb3 } from '../../context/Web3Context';
|
||||||
|
import { DEPLOYMENTS } from '@wtfx/contracts';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
import { PlusCircle, Trash2, Rocket, Check, AlertCircle, Loader2, Sparkles, HelpCircle } from 'lucide-react';
|
||||||
|
|
||||||
|
export const UserMarketWizard: React.FC = () => {
|
||||||
|
const { account, signer, getCollateralContract, isCorrectNetwork } = useWeb3();
|
||||||
|
|
||||||
|
const [question, setQuestion] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
const [category, setCategory] = useState('Crypto');
|
||||||
|
const [customCategory, setCustomCategory] = useState('');
|
||||||
|
const [endTime, setEndTime] = useState(
|
||||||
|
new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString().slice(0, 16)
|
||||||
|
);
|
||||||
|
const [outcomes, setOutcomes] = useState<string[]>(['YES', 'NO']);
|
||||||
|
const [seedAmount, setSeedAmount] = useState<string>('100');
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [statusMsg, setStatusMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
|
const addOutcome = () => {
|
||||||
|
if (outcomes.length < 255) {
|
||||||
|
setOutcomes([...outcomes, `选项 ${outcomes.length + 1}`]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeOutcome = (index: number) => {
|
||||||
|
if (outcomes.length > 2) {
|
||||||
|
setOutcomes(outcomes.filter((_, i) => i !== index));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateOutcome = (index: number, val: string) => {
|
||||||
|
const updated = [...outcomes];
|
||||||
|
updated[index] = val;
|
||||||
|
setOutcomes(updated);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeployMarket = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!signer || !account) {
|
||||||
|
setStatusMsg({ type: 'error', text: '请先连接钱包!' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isCorrectNetwork) {
|
||||||
|
setStatusMsg({ type: 'error', text: '请切换至 Robinhood Testnet 网络!' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setStatusMsg(null);
|
||||||
|
|
||||||
|
const controllerAddress = DEPLOYMENTS.robinhoodTestnet.contracts.controllerProxy;
|
||||||
|
const collateralContract = getCollateralContract();
|
||||||
|
if (!collateralContract) throw new Error('Collateral contract not loaded');
|
||||||
|
|
||||||
|
const seedWei = ethers.parseUnits(seedAmount || '0', 18);
|
||||||
|
|
||||||
|
// Step 1: Approve Seed
|
||||||
|
if (seedWei > 0n) {
|
||||||
|
setStatusMsg({ type: 'success', text: '步骤 1/3: 正在授权初始底仓 WUSD 资金...' });
|
||||||
|
const appTx = await collateralContract.approve(controllerAddress, ethers.MaxUint256);
|
||||||
|
await appTx.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Deploy Market on Controller
|
||||||
|
setStatusMsg({ type: 'success', text: '步骤 2/3: 正在链上部署专属 Power LDA 预测市场...' });
|
||||||
|
const deadlineSec = Math.floor(new Date(endTime).getTime() / 1000);
|
||||||
|
const questionId = ethers.keccak256(
|
||||||
|
ethers.toUtf8Bytes(`${question}-${Date.now()}-${account}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
const controllerAbi = [
|
||||||
|
"function deployMarket((bytes32 questionId, string question, bytes ancillaryData, address rewardToken, uint256 reward, uint256 proposalBond, uint256 earlyResolutionBond, uint256 settlementResolutionBond, uint256 resolutionTime, uint256 numOutcomes) paramsQuestion, (uint256 tier, address creator, uint80 feeRate, uint80 creatorShare, address curve, address collateral) paramsMarket, address oracle, uint256 otSeed) external returns (address)"
|
||||||
|
];
|
||||||
|
const controller = new ethers.Contract(controllerAddress, controllerAbi, signer);
|
||||||
|
|
||||||
|
const questionParams = {
|
||||||
|
questionId: questionId,
|
||||||
|
question: question,
|
||||||
|
ancillaryData: "0x", // 元数据存储在后端数据库
|
||||||
|
rewardToken: DEPLOYMENTS.robinhoodTestnet.contracts.collateral,
|
||||||
|
reward: 0,
|
||||||
|
proposalBond: 0,
|
||||||
|
earlyResolutionBond: 0,
|
||||||
|
settlementResolutionBond: 0,
|
||||||
|
resolutionTime: deadlineSec,
|
||||||
|
numOutcomes: outcomes.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const marketParams = {
|
||||||
|
tier: 1, // 默认标准档位
|
||||||
|
creator: account,
|
||||||
|
feeRate: ethers.parseUnits('0.006', 18),
|
||||||
|
creatorShare: ethers.parseUnits('0.5', 18),
|
||||||
|
curve: DEPLOYMENTS.robinhoodTestnet.contracts.powerLDACurve,
|
||||||
|
collateral: DEPLOYMENTS.robinhoodTestnet.contracts.collateral,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await controller.deployMarket(
|
||||||
|
questionParams,
|
||||||
|
marketParams,
|
||||||
|
account, // 用户本人作为预言机和裁决人!
|
||||||
|
seedWei
|
||||||
|
);
|
||||||
|
|
||||||
|
setStatusMsg({ type: 'success', text: `步骤 3/3: 正在上链确认并同步链下元数据 (Tx: ${tx.hash.slice(0, 10)}...)...` });
|
||||||
|
const receipt = await tx.wait();
|
||||||
|
|
||||||
|
setStatusMsg({
|
||||||
|
type: 'success',
|
||||||
|
text: `🎉 预测市场创建成功!你作为创建者享有 50% 手续费返佣并可在到期后自主裁决结果。`,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('User market deploy error:', err);
|
||||||
|
setStatusMsg({ type: 'error', text: err.reason || err.message || '建盘失败,请检查参数' });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto space-y-6">
|
||||||
|
<form onSubmit={handleDeployMarket} className="bg-[#0f111d] border border-slate-800 rounded-3xl p-6 sm:p-8 shadow-2xl space-y-6">
|
||||||
|
<div className="border-b border-slate-800 pb-5 flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="p-3 bg-gradient-to-tr from-cyan-500/20 to-indigo-500/20 rounded-2xl border border-cyan-500/30 text-cyan-400">
|
||||||
|
<Rocket className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold text-white">自由创建预测市场 (Create Prediction Market)</h2>
|
||||||
|
<p className="text-xs text-slate-400 mt-0.5">
|
||||||
|
任何人均可自由创建 2~255 个选项的预测市场,享受 50% 交易手续费分成与自主裁决权
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 1. 基本信息 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-semibold text-slate-300 mb-1.5">预测事件标题 / 问题 (Question)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={question}
|
||||||
|
onChange={(e) => setQuestion(e.target.value)}
|
||||||
|
placeholder="例如:2026 年底前人类能否首次成功登月?"
|
||||||
|
className="w-full bg-slate-950 border border-slate-700 rounded-xl px-4 py-3 text-sm text-slate-100 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-semibold text-slate-300 mb-1.5">事件类别 (Category)</label>
|
||||||
|
<select
|
||||||
|
value={category}
|
||||||
|
onChange={(e) => setCategory(e.target.value)}
|
||||||
|
className="w-full bg-slate-950 border border-slate-700 rounded-xl px-4 py-3 text-sm text-slate-100 focus:outline-none focus:border-cyan-500"
|
||||||
|
>
|
||||||
|
<option value="Crypto">Crypto (加密货币)</option>
|
||||||
|
<option value="Macro">Macro (宏观经济)</option>
|
||||||
|
<option value="Politics">Politics (政治事件)</option>
|
||||||
|
<option value="Sports">Sports (体育赛事)</option>
|
||||||
|
<option value="Tech">Tech (科技突破)</option>
|
||||||
|
<option value="Others">Others (其他自定义)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-semibold text-slate-300 mb-1.5">预测结算截止时间 (Resolution Deadline)</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
required
|
||||||
|
value={endTime}
|
||||||
|
onChange={(e) => setEndTime(e.target.value)}
|
||||||
|
className="w-full bg-slate-950 border border-slate-700 rounded-xl px-4 py-3 text-sm text-slate-100 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-semibold text-slate-300 mb-1.5">补充裁决说明与判决依据 (Resolution Rules - 链下存储)</label>
|
||||||
|
<textarea
|
||||||
|
rows={2}
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
placeholder="详细说明判断胜出的数据源、规则和依据,将安全保存在后端并向所有参与者展示..."
|
||||||
|
className="w-full bg-slate-950 border border-slate-700 rounded-xl px-4 py-2.5 text-sm text-slate-100 focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2. 结果选项 (2 ~ 255 项) */}
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-xs font-bold text-slate-300 uppercase tracking-wider">
|
||||||
|
结果选项配置 (Outcomes: {outcomes.length}/255)
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addOutcome}
|
||||||
|
disabled={outcomes.length >= 255}
|
||||||
|
className="text-xs text-cyan-400 hover:text-cyan-300 flex items-center space-x-1"
|
||||||
|
>
|
||||||
|
<PlusCircle className="w-4 h-4" />
|
||||||
|
<span>添加选项</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-h-60 overflow-y-auto pr-1">
|
||||||
|
{outcomes.map((outcome, idx) => (
|
||||||
|
<div key={idx} className="flex items-center space-x-2 bg-slate-950 border border-slate-800 rounded-xl p-2.5">
|
||||||
|
<span className="w-6 text-center font-mono text-xs text-slate-500">#{idx}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={outcome}
|
||||||
|
onChange={(e) => updateOutcome(idx, e.target.value)}
|
||||||
|
className="flex-1 bg-transparent text-sm text-slate-200 focus:outline-none"
|
||||||
|
/>
|
||||||
|
{outcomes.length > 2 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeOutcome(idx)}
|
||||||
|
className="text-slate-500 hover:text-rose-400 p-1"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3. 初始底仓资金 */}
|
||||||
|
<div className="space-y-2 pt-2">
|
||||||
|
<label className="block text-xs font-semibold text-slate-300">
|
||||||
|
初始底仓资金 (Initial Seed WUSD - 任意自定)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={seedAmount}
|
||||||
|
onChange={(e) => setSeedAmount(e.target.value)}
|
||||||
|
placeholder="100"
|
||||||
|
className="w-full bg-slate-950 border border-slate-700 rounded-xl px-4 py-3 text-sm text-slate-100 font-mono focus:outline-none focus:border-cyan-500"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-slate-500">
|
||||||
|
底仓金额由你自由决定,将均匀注入各个选项池中,为你启动流动性并赚取全生命周期 50% 手续费。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 提交按钮 */}
|
||||||
|
<div className="pt-4 border-t border-slate-800 flex items-center justify-between">
|
||||||
|
<div className="text-xs text-slate-400">
|
||||||
|
创建者即为市场初裁人 • 到期后进入 24 小时争议公示期
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading || !account}
|
||||||
|
className="px-6 py-3.5 bg-gradient-to-r from-cyan-500 to-indigo-600 hover:from-cyan-400 hover:to-indigo-500 text-white font-bold rounded-xl shadow-lg shadow-cyan-500/25 flex items-center space-x-2 transition-all disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
<span>正在部署中...</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Rocket className="w-4 h-4" />
|
||||||
|
<span>立即发布预测市场</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 状态通知 */}
|
||||||
|
{statusMsg && (
|
||||||
|
<div
|
||||||
|
className={`p-4 rounded-2xl border flex items-start space-x-3 text-xs ${
|
||||||
|
statusMsg.type === 'success'
|
||||||
|
? 'bg-emerald-950/40 border-emerald-800/60 text-emerald-300'
|
||||||
|
: 'bg-rose-950/40 border-rose-800/60 text-rose-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{statusMsg.type === 'success' ? (
|
||||||
|
<Check className="w-5 h-5 flex-shrink-0 text-emerald-400" />
|
||||||
|
) : (
|
||||||
|
<AlertCircle className="w-5 h-5 flex-shrink-0 text-rose-400" />
|
||||||
|
)}
|
||||||
|
<div className="flex-1 overflow-hidden break-words">
|
||||||
|
<p>{statusMsg.text}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user