169 lines
7.2 KiB
TypeScript
169 lines
7.2 KiB
TypeScript
/**
|
|
* Chain Management Dashboard
|
|
* Admin UI for managing all supported chains, adapters, and deployments
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useAccount } from 'wagmi';
|
|
import { ethers } from 'ethers';
|
|
import toast from 'react-hot-toast';
|
|
|
|
interface ChainMetadata {
|
|
chainId: number;
|
|
chainIdentifier: string;
|
|
chainType: string;
|
|
adapter: string;
|
|
isActive: boolean;
|
|
explorerUrl: string;
|
|
minConfirmations: number;
|
|
avgBlockTime: number;
|
|
}
|
|
|
|
export default function ChainManagementDashboard() {
|
|
const { address, isConnected } = useAccount();
|
|
const [chains, setChains] = useState<ChainMetadata[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [selectedChain, setSelectedChain] = useState<string>('');
|
|
|
|
useEffect(() => {
|
|
if (isConnected) {
|
|
loadChains();
|
|
}
|
|
}, [isConnected, address]);
|
|
|
|
const loadChains = async () => {
|
|
try {
|
|
setLoading(true);
|
|
// TODO: Connect to ChainRegistry contract
|
|
// const provider = new ethers.JsonRpcProvider(process.env.NEXT_PUBLIC_RPC_URL);
|
|
// const registry = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, provider);
|
|
// const [evmChainIds, evmChains] = await registry.getAllEVMChains();
|
|
// const [nonEvmIds, nonEvmChains] = await registry.getAllNonEVMChains();
|
|
|
|
// Mock data for now
|
|
setChains([
|
|
{
|
|
chainId: 138,
|
|
chainIdentifier: 'EVM-138',
|
|
chainType: 'EVM',
|
|
adapter: '0x...',
|
|
isActive: true,
|
|
explorerUrl: 'https://explorer.d-bis.org',
|
|
minConfirmations: 12,
|
|
avgBlockTime: 2
|
|
},
|
|
{
|
|
chainId: 50,
|
|
chainIdentifier: 'EVM-50',
|
|
chainType: 'XDC',
|
|
adapter: '0x...',
|
|
isActive: true,
|
|
explorerUrl: 'https://explorer.xdc.network',
|
|
minConfirmations: 12,
|
|
avgBlockTime: 2
|
|
}
|
|
]);
|
|
} catch (error: any) {
|
|
toast.error(`Failed to load chains: ${error.message}`);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const toggleChain = async (chainId: number, chainIdentifier: string, currentStatus: boolean) => {
|
|
try {
|
|
// TODO: Call ChainRegistry.setChainActive()
|
|
toast.success(`Chain ${currentStatus ? 'disabled' : 'enabled'}`);
|
|
loadChains();
|
|
} catch (error: any) {
|
|
toast.error(`Failed to toggle chain: ${error.message}`);
|
|
}
|
|
};
|
|
|
|
if (!isConnected) {
|
|
return (
|
|
<div className="p-6 bg-white/10 rounded-xl">
|
|
<p className="text-white/70">Please connect your wallet to manage chains.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="bg-black/20 rounded-xl p-6 border border-white/10">
|
|
<h2 className="text-xl font-bold text-white mb-4">Chain Management</h2>
|
|
|
|
{loading ? (
|
|
<div className="text-white/70">Loading chains...</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{chains.map((chain) => (
|
|
<div
|
|
key={chain.chainId || chain.chainIdentifier}
|
|
className="bg-white/5 rounded-lg p-4 flex items-center justify-between"
|
|
>
|
|
<div>
|
|
<h3 className="text-white font-semibold">
|
|
{chain.chainIdentifier} ({chain.chainType})
|
|
</h3>
|
|
<p className="text-white/70 text-sm">
|
|
Adapter: {chain.adapter.slice(0, 10)}...
|
|
</p>
|
|
<p className="text-white/70 text-sm">
|
|
Explorer: <a href={chain.explorerUrl} target="_blank" rel="noopener noreferrer" className="text-blue-400 hover:underline">{chain.explorerUrl}</a>
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<span className={`px-3 py-1 rounded-full text-sm ${
|
|
chain.isActive
|
|
? 'bg-green-500/20 text-green-200'
|
|
: 'bg-red-500/20 text-red-200'
|
|
}`}>
|
|
{chain.isActive ? 'Active' : 'Inactive'}
|
|
</span>
|
|
<button
|
|
onClick={() => toggleChain(chain.chainId, chain.chainIdentifier, chain.isActive)}
|
|
className={`px-4 py-2 rounded-lg font-semibold transition-colors ${
|
|
chain.isActive
|
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
|
: 'bg-green-600 hover:bg-green-700 text-white'
|
|
}`}
|
|
>
|
|
{chain.isActive ? 'Disable' : 'Enable'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="bg-black/20 rounded-xl p-6 border border-white/10">
|
|
<h3 className="text-lg font-bold text-white mb-4">Add New Chain</h3>
|
|
<div className="space-y-4">
|
|
<select
|
|
value={selectedChain}
|
|
onChange={(e) => setSelectedChain(e.target.value)}
|
|
className="w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white"
|
|
>
|
|
<option value="">Select chain to add...</option>
|
|
<option value="polygon">Polygon</option>
|
|
<option value="arbitrum">Arbitrum</option>
|
|
<option value="optimism">Optimism</option>
|
|
<option value="base">Base</option>
|
|
<option value="avalanche">Avalanche</option>
|
|
<option value="bsc">BSC</option>
|
|
<option value="ethereum">Ethereum Mainnet</option>
|
|
</select>
|
|
<button
|
|
onClick={() => toast.info('Deployment feature coming soon')}
|
|
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-semibold"
|
|
>
|
|
Deploy Chain Adapter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|