Files
aitbc/apps/marketplace/src/pages/StakingDashboard.tsx
oib 15427c96c0 chore: update file permissions to executable across repository
- Change file mode from 644 to 755 for all project files
- Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet"
- Rename Miner.extra_meta_data to extra_metadata for consistency
2026-03-06 22:17:54 +01:00

918 lines
34 KiB
TypeScript
Executable File

import React, { useState, useEffect } from 'react';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Progress } from '@/components/ui/progress';
import { Alert, AlertDescription } from '@/components/ui/alert';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Separator } from '@/components/ui/separator';
import {
TrendingUp,
TrendingDown,
Wallet,
Clock,
AlertTriangle,
CheckCircle,
XCircle,
Calculator,
Shield,
Zap,
Star,
Info,
ArrowUpRight,
ArrowDownRight,
Coins,
BarChart3,
PieChart,
Activity
} from 'lucide-react';
import { useWallet } from '@/hooks/use-wallet';
import { useToast } from '@/hooks/use-toast';
import { formatDistanceToNow, format } from 'date-fns';
interface Stake {
stake_id: string;
staker_address: string;
agent_wallet: string;
amount: number;
lock_period: number;
start_time: string;
end_time: string;
status: 'active' | 'unbonding' | 'completed' | 'slashed';
accumulated_rewards: number;
last_reward_time: string;
current_apy: number;
agent_tier: 'bronze' | 'silver' | 'gold' | 'platinum' | 'diamond';
performance_multiplier: number;
auto_compound: boolean;
unbonding_time?: string;
early_unbond_penalty: number;
lock_bonus_multiplier: number;
}
interface AgentMetrics {
agent_wallet: string;
total_staked: number;
staker_count: number;
total_rewards_distributed: number;
average_accuracy: number;
total_submissions: number;
successful_submissions: number;
success_rate: number;
current_tier: 'bronze' | 'silver' | 'gold' | 'platinum' | 'diamond';
tier_score: number;
reputation_score: number;
last_update_time: string;
average_response_time?: number;
energy_efficiency_score?: number;
}
interface StakingPool {
agent_wallet: string;
total_staked: number;
total_rewards: number;
pool_apy: number;
staker_count: number;
active_stakers: string[];
last_distribution_time: string;
min_stake_amount: number;
max_stake_amount: number;
auto_compound_enabled: boolean;
pool_performance_score: number;
volatility_score: number;
}
const StakingDashboard: React.FC = () => {
const { address, isConnected } = useWallet();
const { toast } = useToast();
const [stakes, setStakes] = useState<Stake[]>([]);
const [supportedAgents, setSupportedAgents] = useState<AgentMetrics[]>([]);
const [stakingPools, setStakingPools] = useState<StakingPool[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState('my-stakes');
const [showCreateStakeModal, setShowCreateStakeModal] = useState(false);
const [selectedAgent, setSelectedAgent] = useState<AgentMetrics | null>(null);
const [stakeForm, setStakeForm] = useState({
agent_wallet: '',
amount: '',
lock_period: '30',
auto_compound: false
});
const [totalRewards, setTotalRewards] = useState(0);
const [totalStaked, setTotalStaked] = useState(0);
// Load data on component mount
useEffect(() => {
if (isConnected) {
loadMyStakes();
loadMyRewards();
}
loadSupportedAgents();
loadStakingPools();
}, [isConnected]);
const loadMyStakes = async () => {
try {
const response = await fetch('/api/v1/staking/my-positions', {
headers: { 'Authorization': `Bearer ${address}` }
});
if (response.ok) {
const data = await response.json();
setStakes(data);
// Calculate total staked
const total = data.reduce((sum: number, stake: Stake) => sum + stake.amount, 0);
setTotalStaked(total);
}
} catch (error) {
console.error('Error loading stakes:', error);
}
};
const loadMyRewards = async () => {
try {
const response = await fetch('/api/v1/staking/my-rewards?period=monthly', {
headers: { 'Authorization': `Bearer ${address}` }
});
if (response.ok) {
const data = await response.json();
setTotalRewards(data.total_rewards);
}
} catch (error) {
console.error('Error loading rewards:', error);
}
};
const loadSupportedAgents = async () => {
try {
setLoading(true);
const response = await fetch('/api/v1/staking/agents/supported?limit=50');
if (response.ok) {
const data = await response.json();
setSupportedAgents(data.agents);
}
} catch (error) {
console.error('Error loading agents:', error);
} finally {
setLoading(false);
}
};
const loadStakingPools = async () => {
try {
const response = await fetch('/api/v1/staking/pools');
if (response.ok) {
const data = await response.json();
setStakingPools(data);
}
} catch (error) {
console.error('Error loading pools:', error);
}
};
const handleCreateStake = async () => {
if (!isConnected) {
toast({
title: 'Wallet Required',
description: 'Please connect your wallet to create stakes',
variant: 'destructive'
});
return;
}
try {
const response = await fetch('/api/v1/stake', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${address}`
},
body: JSON.stringify(stakeForm)
});
if (response.ok) {
const newStake = await response.json();
setStakes(prev => [newStake, ...prev]);
setShowCreateStakeModal(false);
setStakeForm({ agent_wallet: '', amount: '', lock_period: '30', auto_compound: false });
toast({
title: 'Stake Created',
description: `Successfully staked ${stakeForm.amount} AITBC`,
});
// Reload data
loadMyStakes();
loadStakingPools();
} else {
throw new Error('Failed to create stake');
}
} catch (error) {
console.error('Error creating stake:', error);
toast({
title: 'Error',
description: 'Failed to create stake',
variant: 'destructive'
});
}
};
const handleUnbondStake = async (stakeId: string) => {
try {
const response = await fetch(`/api/v1/stake/${stakeId}/unbond`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${address}` }
});
if (response.ok) {
toast({
title: 'Unbonding Initiated',
description: 'Your stake is now in the unbonding period',
});
// Reload stakes
loadMyStakes();
} else {
throw new Error('Failed to unbond stake');
}
} catch (error) {
console.error('Error unbonding stake:', error);
toast({
title: 'Error',
description: 'Failed to unbond stake',
variant: 'destructive'
});
}
};
const handleCompleteUnbonding = async (stakeId: string) => {
try {
const response = await fetch(`/api/v1/stake/${stakeId}/complete`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${address}` }
});
if (response.ok) {
const result = await response.json();
toast({
title: 'Unbonding Completed',
description: `Received ${result.total_amount + result.total_rewards} AITBC`,
});
// Reload stakes and rewards
loadMyStakes();
loadMyRewards();
} else {
throw new Error('Failed to complete unbonding');
}
} catch (error) {
console.error('Error completing unbonding:', error);
toast({
title: 'Error',
description: 'Failed to complete unbonding',
variant: 'destructive'
});
}
};
const getTierColor = (tier: string) => {
const colors = {
bronze: 'bg-orange-100 text-orange-800 border-orange-200',
silver: 'bg-gray-100 text-gray-800 border-gray-200',
gold: 'bg-yellow-100 text-yellow-800 border-yellow-200',
platinum: 'bg-purple-100 text-purple-800 border-purple-200',
diamond: 'bg-blue-100 text-blue-800 border-blue-200'
};
return colors[tier as keyof typeof colors] || colors.bronze;
};
const getStatusColor = (status: string) => {
const colors = {
active: 'bg-green-100 text-green-800',
unbonding: 'bg-yellow-100 text-yellow-800',
completed: 'bg-blue-100 text-blue-800',
slashed: 'bg-red-100 text-red-800'
};
return colors[status as keyof typeof colors] || colors.active;
};
const getTimeRemaining = (endTime: string) => {
const endDate = new Date(endTime);
const now = new Date();
const timeRemaining = endDate.getTime() - now.getTime();
if (timeRemaining <= 0) return 'Expired';
return formatDistanceToNow(endDate, { addSuffix: true });
};
const calculateAPY = (agent: AgentMetrics, lockPeriod: number) => {
const baseAPY = 5.0;
const tierMultipliers = {
bronze: 1.0,
silver: 1.2,
gold: 1.5,
platinum: 2.0,
diamond: 3.0
};
const lockMultipliers = {
30: 1.1,
90: 1.25,
180: 1.5,
365: 2.0
};
const tierMultiplier = tierMultipliers[agent.current_tier as keyof typeof tierMultipliers];
const lockMultiplier = lockMultipliers[lockPeriod as keyof typeof lockMultipliers] || 1.0;
const apy = baseAPY * tierMultiplier * lockMultiplier;
return Math.min(apy, 20.0); // Cap at 20%
};
const getRiskLevel = (agent: AgentMetrics) => {
if (agent.success_rate >= 90 && agent.average_accuracy >= 90) return 'low';
if (agent.success_rate >= 70 && agent.average_accuracy >= 70) return 'medium';
return 'high';
};
const getRiskColor = (risk: string) => {
const colors = {
low: 'text-green-600',
medium: 'text-yellow-600',
high: 'text-red-600'
};
return colors[risk as keyof typeof colors] || colors.medium;
};
if (loading) {
return (
<div className="container mx-auto py-8">
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>
);
}
return (
<div className="container mx-auto py-8 space-y-6">
{/* Header */}
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold">Staking Dashboard</h1>
<p className="text-muted-foreground">
Stake AITBC tokens on AI agents and earn rewards based on performance
</p>
</div>
{isConnected && (
<Button onClick={() => setShowCreateStakeModal(true)}>
Create Stake
</Button>
)}
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Total Staked</p>
<p className="text-2xl font-bold">{totalStaked.toLocaleString()}</p>
<p className="text-xs text-muted-foreground">AITBC</p>
</div>
<Coins className="h-8 w-8 text-blue-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Total Rewards</p>
<p className="text-2xl font-bold">{totalRewards.toLocaleString()}</p>
<p className="text-xs text-muted-foreground">AITBC</p>
</div>
<TrendingUp className="h-8 w-8 text-green-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Active Stakes</p>
<p className="text-2xl font-bold">{stakes.filter(s => s.status === 'active').length}</p>
<p className="text-xs text-muted-foreground">Positions</p>
</div>
<Shield className="h-8 w-8 text-purple-600" />
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Average APY</p>
<p className="text-2xl font-bold">
{stakes.length > 0
? (stakes.reduce((sum, s) => sum + s.current_apy, 0) / stakes.length).toFixed(1)
: '0.0'
}%
</p>
<p className="text-xs text-muted-foreground">Annual Yield</p>
</div>
<BarChart3 className="h-8 w-8 text-orange-600" />
</div>
</CardContent>
</Card>
</div>
{/* Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="my-stakes">My Stakes</TabsTrigger>
<TabsTrigger value="agents">Available Agents</TabsTrigger>
<TabsTrigger value="pools">Staking Pools</TabsTrigger>
{isConnected && <TabsTrigger value="rewards">Rewards</TabsTrigger>}
</TabsList>
{/* My Stakes Tab */}
<TabsContent value="my-stakes" className="space-y-4">
{!isConnected ? (
<Alert>
<Wallet className="h-4 w-4" />
<AlertDescription>
Connect your wallet to view your staking positions
</AlertDescription>
</Alert>
) : stakes.length === 0 ? (
<div className="text-center py-12">
<Shield className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">No Stakes Found</h3>
<p className="text-muted-foreground mb-4">
Start staking on AI agents to earn rewards
</p>
<Button onClick={() => setShowCreateStakeModal(true)}>
Create Your First Stake
</Button>
</div>
) : (
<div className="space-y-4">
{stakes.map((stake) => (
<Card key={stake.stake_id}>
<CardHeader>
<div className="flex justify-between items-start">
<div className="space-y-2">
<CardTitle className="text-lg">
{stake.agent_wallet.slice(0, 8)}...{stake.agent_wallet.slice(-6)}
</CardTitle>
<div className="flex gap-2">
<Badge className={getTierColor(stake.agent_tier)}>
{stake.agent_tier.charAt(0).toUpperCase() + stake.agent_tier.slice(1)}
</Badge>
<Badge className={getStatusColor(stake.status)}>
{stake.status.charAt(0).toUpperCase() + stake.status.slice(1)}
</Badge>
{stake.auto_compound && (
<Badge variant="secondary">
<Zap className="h-3 w-3 mr-1" />
Auto-Compound
</Badge>
)}
</div>
</div>
<div className="text-right">
<p className="text-2xl font-bold text-blue-600">
{stake.amount.toLocaleString()}
</p>
<p className="text-sm text-muted-foreground">AITBC</p>
<p className="text-sm font-medium text-green-600">
{stake.current_apy.toFixed(1)}% APY
</p>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<p className="text-sm text-muted-foreground">Lock Period</p>
<p className="font-medium">{stake.lock_period} days</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Time Remaining</p>
<p className="font-medium">{getTimeRemaining(stake.end_time)}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Accumulated Rewards</p>
<p className="font-medium text-green-600">
{stake.accumulated_rewards.toFixed(2)} AITBC
</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Performance Multiplier</p>
<p className="font-medium">{stake.performance_multiplier}x</p>
</div>
</div>
{/* Progress bar for lock period */}
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-muted-foreground">Lock Progress</span>
<span className="font-medium">
{Math.max(0, 100 - ((new Date(stake.end_time).getTime() - Date.now()) / (stake.lock_period * 24 * 60 * 60 * 1000) * 100)).toFixed(1)}%
</span>
</div>
<Progress
value={Math.max(0, 100 - ((new Date(stake.end_time).getTime() - Date.now()) / (stake.lock_period * 24 * 60 * 60 * 1000) * 100))}
className="h-2"
/>
</div>
</CardContent>
<CardFooter className="flex gap-2">
{stake.status === 'active' && new Date(stake.end_time) <= new Date() && (
<Button
variant="outline"
onClick={() => handleUnbondStake(stake.stake_id)}
>
<Clock className="h-4 w-4 mr-2" />
Initiate Unbonding
</Button>
)}
{stake.status === 'unbonding' && (
<Button
onClick={() => handleCompleteUnbonding(stake.stake_id)}
>
<CheckCircle className="h-4 w-4 mr-2" />
Complete Unbonding
</Button>
)}
<Button variant="outline" size="sm">
View Details
</Button>
</CardFooter>
</Card>
))}
</div>
)}
</TabsContent>
{/* Available Agents Tab */}
<TabsContent value="agents" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{supportedAgents.map((agent) => (
<Card key={agent.agent_wallet} className="hover:shadow-lg transition-shadow">
<CardHeader>
<div className="flex justify-between items-start">
<div className="space-y-2">
<CardTitle className="text-lg">
{agent.agent_wallet.slice(0, 8)}...{agent.agent_wallet.slice(-6)}
</CardTitle>
<div className="flex gap-2">
<Badge className={getTierColor(agent.current_tier)}>
{agent.current_tier.charAt(0).toUpperCase() + agent.current_tier.slice(1)}
</Badge>
<Badge className={getRiskColor(getRiskLevel(agent))}>
{getRiskLevel(agent).toUpperCase()} RISK
</Badge>
</div>
</div>
<div className="text-right">
<p className="text-2xl font-bold text-blue-600">
{calculateAPY(agent, 30).toFixed(1)}%
</p>
<p className="text-sm text-muted-foreground">APY</p>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-muted-foreground">Total Staked</p>
<p className="font-medium">{agent.total_staked.toLocaleString()}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Stakers</p>
<p className="font-medium">{agent.staker_count}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Accuracy</p>
<p className="font-medium">{agent.average_accuracy.toFixed(1)}%</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Success Rate</p>
<p className="font-medium">{agent.success_rate.toFixed(1)}%</p>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Total Submissions</span>
<span className="font-medium">{agent.total_submissions}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Rewards Distributed</span>
<span className="font-medium text-green-600">
{agent.total_rewards_distributed.toLocaleString()} AITBC
</span>
</div>
</div>
{/* Performance indicators */}
<div className="flex items-center gap-2 text-sm">
<Activity className="h-4 w-4 text-blue-600" />
<span>Performance Score: {agent.tier_score.toFixed(1)}</span>
</div>
</CardContent>
<CardFooter>
<Button
className="w-full"
onClick={() => {
setSelectedAgent(agent);
setStakeForm(prev => ({ ...prev, agent_wallet: agent.agent_wallet }));
setShowCreateStakeModal(true);
}}
disabled={!isConnected}
>
{isConnected ? 'Stake on Agent' : 'Connect Wallet'}
</Button>
</CardFooter>
</Card>
))}
</div>
</TabsContent>
{/* Staking Pools Tab */}
<TabsContent value="pools" className="space-y-4">
<Table>
<TableHeader>
<TableRow>
<TableHead>Agent</TableHead>
<TableHead>Total Staked</TableHead>
<TableHead>Pool APY</TableHead>
<TableHead>Stakers</TableHead>
<TableHead>Total Rewards</TableHead>
<TableHead>Performance</TableHead>
<TableHead>Volatility</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{stakingPools.map((pool) => (
<TableRow key={pool.agent_wallet}>
<TableCell className="font-mono">
{pool.agent_wallet.slice(0, 8)}...{pool.agent_wallet.slice(-6)}
</TableCell>
<TableCell>{pool.total_staked.toLocaleString()} AITBC</TableCell>
<TableCell>
<span className="text-green-600 font-medium">{pool.pool_apy.toFixed(1)}%</span>
</TableCell>
<TableCell>{pool.staker_count}</TableCell>
<TableCell className="text-green-600">
{pool.total_rewards.toLocaleString()} AITBC
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Progress value={pool.pool_performance_score} className="w-16 h-2" />
<span className="text-sm">{pool.pool_performance_score.toFixed(0)}</span>
</div>
</TableCell>
<TableCell>
<Badge variant={pool.volatility_score < 30 ? 'secondary' : pool.volatility_score < 70 ? 'default' : 'destructive'}>
{pool.volatility_score < 30 ? 'Low' : pool.volatility_score < 70 ? 'Medium' : 'High'}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TabsContent>
{/* Rewards Tab */}
<TabsContent value="rewards" className="space-y-4">
{!isConnected ? (
<Alert>
<Wallet className="h-4 w-4" />
<AlertDescription>
Connect your wallet to view your rewards
</AlertDescription>
</Alert>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Coins className="h-5 w-5" />
Reward Summary
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-muted-foreground">Total Earned</span>
<span className="font-bold text-green-600">
{totalRewards.toLocaleString()} AITBC
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Pending Rewards</span>
<span className="font-bold">
{stakes.reduce((sum, s) => sum + s.accumulated_rewards, 0).toLocaleString()} AITBC
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Average APY</span>
<span className="font-bold">
{stakes.length > 0
? (stakes.reduce((sum, s) => sum + s.current_apy, 0) / stakes.length).toFixed(1)
: '0.0'
}%
</span>
</div>
</div>
<Separator />
<Button className="w-full">
Claim All Rewards
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<PieChart className="h-5 w-5" />
Reward History
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center py-8 text-muted-foreground">
<Info className="h-8 w-8 mx-auto mb-2" />
<p>Reward history will be available soon</p>
</div>
</CardContent>
</Card>
</div>
)}
</TabsContent>
</Tabs>
{/* Create Stake Modal */}
{showCreateStakeModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
<Card className="max-w-md w-full">
<CardHeader>
<CardTitle>Create New Stake</CardTitle>
<CardDescription>
Stake AITBC tokens on an AI agent to earn rewards
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="text-sm font-medium">Agent</label>
<Select
value={stakeForm.agent_wallet}
onValueChange={(value) => setStakeForm(prev => ({ ...prev, agent_wallet: value }))}
>
<SelectTrigger>
<SelectValue placeholder="Select an agent" />
</SelectTrigger>
<SelectContent>
{supportedAgents.map((agent) => (
<SelectItem key={agent.agent_wallet} value={agent.agent_wallet}>
<div className="flex items-center justify-between w-full">
<span>{agent.agent_wallet.slice(0, 8)}...{agent.agent_wallet.slice(-6)}</span>
<span className="text-green-600">{calculateAPY(agent, parseInt(stakeForm.lock_period)).toFixed(1)}% APY</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<label className="text-sm font-medium">Amount (AITBC)</label>
<Input
type="number"
placeholder="100.0"
value={stakeForm.amount}
onChange={(e) => setStakeForm(prev => ({ ...prev, amount: e.target.value }))}
min="100"
max="100000"
/>
<p className="text-xs text-muted-foreground mt-1">
Min: 100 AITBC, Max: 100,000 AITBC
</p>
</div>
<div>
<label className="text-sm font-medium">Lock Period</label>
<Select
value={stakeForm.lock_period}
onValueChange={(value) => setStakeForm(prev => ({ ...prev, lock_period: value }))}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="30">30 days (1.1x multiplier)</SelectItem>
<SelectItem value="90">90 days (1.25x multiplier)</SelectItem>
<SelectItem value="180">180 days (1.5x multiplier)</SelectItem>
<SelectItem value="365">365 days (2.0x multiplier)</SelectItem>
</SelectContent>
</Select>
</div>
{selectedAgent && (
<div className="bg-muted p-3 rounded">
<h4 className="font-medium mb-2">Estimated Returns</h4>
<div className="space-y-1 text-sm">
<div className="flex justify-between">
<span>Base APY:</span>
<span>5.0%</span>
</div>
<div className="flex justify-between">
<span>Tier Multiplier:</span>
<span>{selectedAgent.current_tier} tier</span>
</div>
<div className="flex justify-between">
<span>Lock Multiplier:</span>
<span>{stakeForm.lock_period === '30' ? '1.1x' : stakeForm.lock_period === '90' ? '1.25x' : stakeForm.lock_period === '180' ? '1.5x' : '2.0x'}</span>
</div>
<div className="flex justify-between font-bold">
<span>Estimated APY:</span>
<span className="text-green-600">
{calculateAPY(selectedAgent, parseInt(stakeForm.lock_period)).toFixed(1)}%
</span>
</div>
</div>
</div>
)}
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="auto-compound"
checked={stakeForm.auto_compound}
onChange={(e) => setStakeForm(prev => ({ ...prev, auto_compound: e.target.checked }))}
/>
<label htmlFor="auto-compound" className="text-sm">
Enable auto-compounding
</label>
</div>
</CardContent>
<CardFooter className="flex gap-2">
<Button
className="flex-1"
onClick={handleCreateStake}
disabled={!stakeForm.agent_wallet || !stakeForm.amount || parseFloat(stakeForm.amount) < 100}
>
Create Stake
</Button>
<Button variant="outline" onClick={() => setShowCreateStakeModal(false)}>
Cancel
</Button>
</CardFooter>
</Card>
</div>
)}
</div>
);
};
export default StakingDashboard;