feat(developer-ecosystem): implement bounty and staking system with ZK-proof integration

Phase 1 Implementation Complete:
- AgentBounty.sol: Automated bounty board with ZK-proof verification
- AgentStaking.sol: Reputation-based yield farming with dynamic APY
- BountyIntegration.sol: Cross-contract event handling and auto-verification
- Database models: Complete bounty, staking, and ecosystem metrics schemas
- REST APIs: Full bounty and staking management endpoints
- Services: Business logic for bounty creation, verification, and staking operations
- Ecosystem dashboard: Analytics and metrics tracking system

Key Features:
- Multi-tier bounty system (Bronze, Silver, Gold, Platinum)
- Performance-based APY calculation with reputation multipliers
- ZK-proof integration with PerformanceVerifier.sol
- Automatic bounty completion detection
- Comprehensive analytics dashboard
- Risk assessment and leaderboards
- Real-time metrics and predictions

Security Features:
- Reentrancy protection on all contracts
- Role-based access control
- Dispute resolution mechanism
- Early unbonding penalties
- Platform fee collection

Economic Model:
- Creation fees: 0.5%
- Success fees: 2%
- Platform fees: 1%
- Staking APY: 5-20% based on performance
- Dispute fees: 0.1%
This commit is contained in:
oib
2026-02-27 17:51:23 +01:00
parent 27e836bf3f
commit a477681c4b
11 changed files with 6695 additions and 0 deletions

View File

@@ -0,0 +1,584 @@
"""
Bounty Management API
REST API for AI agent bounty system with ZK-proof verification
"""
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from pydantic import BaseModel, Field, validator
from ..storage import SessionDep
from ..logging import get_logger
from ..domain.bounty import (
Bounty, BountySubmission, BountyStatus, BountyTier,
SubmissionStatus, BountyStats, BountyIntegration
)
from ..services.bounty_service import BountyService
from ..services.blockchain_service import BlockchainService
from ..auth import get_current_user
logger = get_logger(__name__)
router = APIRouter()
# Pydantic models for request/response
class BountyCreateRequest(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: str = Field(..., min_length=10, max_length=5000)
reward_amount: float = Field(..., gt=0)
tier: BountyTier = Field(default=BountyTier.BRONZE)
performance_criteria: Dict[str, Any] = Field(default_factory=dict)
min_accuracy: float = Field(default=90.0, ge=0, le=100)
max_response_time: Optional[int] = Field(default=None, gt=0)
deadline: datetime = Field(..., gt=datetime.utcnow())
max_submissions: int = Field(default=100, gt=0, le=1000)
requires_zk_proof: bool = Field(default=True)
auto_verify_threshold: float = Field(default=95.0, ge=0, le=100)
tags: List[str] = Field(default_factory=list)
category: Optional[str] = Field(default=None)
difficulty: Optional[str] = Field(default=None)
@validator('deadline')
def validate_deadline(cls, v):
if v <= datetime.utcnow():
raise ValueError('Deadline must be in the future')
if v > datetime.utcnow() + timedelta(days=365):
raise ValueError('Deadline cannot be more than 1 year in the future')
return v
@validator('reward_amount')
def validate_reward_amount(cls, v, values):
tier = values.get('tier', BountyTier.BRONZE)
tier_minimums = {
BountyTier.BRONZE: 100.0,
BountyTier.SILVER: 500.0,
BountyTier.GOLD: 1000.0,
BountyTier.PLATINUM: 5000.0
}
if v < tier_minimums.get(tier, 100.0):
raise ValueError(f'Reward amount must be at least {tier_minimums[tier]} for {tier} tier')
return v
class BountyResponse(BaseModel):
bounty_id: str
title: str
description: str
reward_amount: float
creator_id: str
tier: BountyTier
status: BountyStatus
performance_criteria: Dict[str, Any]
min_accuracy: float
max_response_time: Optional[int]
deadline: datetime
creation_time: datetime
max_submissions: int
submission_count: int
requires_zk_proof: bool
auto_verify_threshold: float
winning_submission_id: Optional[str]
winner_address: Optional[str]
creation_fee: float
success_fee: float
platform_fee: float
tags: List[str]
category: Optional[str]
difficulty: Optional[str]
class BountySubmissionRequest(BaseModel):
bounty_id: str
zk_proof: Optional[Dict[str, Any]] = Field(default=None)
performance_hash: str = Field(..., min_length=1)
accuracy: float = Field(..., ge=0, le=100)
response_time: Optional[int] = Field(default=None, gt=0)
compute_power: Optional[float] = Field(default=None, gt=0)
energy_efficiency: Optional[float] = Field(default=None, ge=0, le=100)
submission_data: Dict[str, Any] = Field(default_factory=dict)
test_results: Dict[str, Any] = Field(default_factory=dict)
class BountySubmissionResponse(BaseModel):
submission_id: str
bounty_id: str
submitter_address: str
accuracy: float
response_time: Optional[int]
compute_power: Optional[float]
energy_efficiency: Optional[float]
zk_proof: Optional[Dict[str, Any]]
performance_hash: str
status: SubmissionStatus
verification_time: Optional[datetime]
verifier_address: Optional[str]
dispute_reason: Optional[str]
dispute_time: Optional[datetime]
dispute_resolved: bool
submission_time: datetime
submission_data: Dict[str, Any]
test_results: Dict[str, Any]
class BountyVerificationRequest(BaseModel):
bounty_id: str
submission_id: str
verified: bool
verifier_address: str
verification_notes: Optional[str] = Field(default=None)
class BountyDisputeRequest(BaseModel):
bounty_id: str
submission_id: str
dispute_reason: str = Field(..., min_length=10, max_length=1000)
class BountyFilterRequest(BaseModel):
status: Optional[BountyStatus] = None
tier: Optional[BountyTier] = None
creator_id: Optional[str] = None
category: Optional[str] = None
min_reward: Optional[float] = Field(default=None, ge=0)
max_reward: Optional[float] = Field(default=None, ge=0)
deadline_before: Optional[datetime] = None
deadline_after: Optional[datetime] = None
tags: Optional[List[str]] = None
requires_zk_proof: Optional[bool] = None
page: int = Field(default=1, ge=1)
limit: int = Field(default=20, ge=1, le=100)
class BountyStatsResponse(BaseModel):
total_bounties: int
active_bounties: int
completed_bounties: int
expired_bounties: int
disputed_bounties: int
total_value_locked: float
total_rewards_paid: float
total_fees_collected: float
average_reward: float
success_rate: float
average_completion_time: Optional[float]
average_accuracy: Optional[float]
unique_creators: int
unique_submitters: int
total_submissions: int
tier_distribution: Dict[str, int]
# Dependency injection
def get_bounty_service(session: SessionDep) -> BountyService:
return BountyService(session)
def get_blockchain_service() -> BlockchainService:
return BlockchainService()
# API endpoints
@router.post("/bounties", response_model=BountyResponse)
async def create_bounty(
request: BountyCreateRequest,
background_tasks: BackgroundTasks,
session: SessionDep,
bounty_service: BountyService = Depends(get_bounty_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Create a new bounty"""
try:
logger.info(f"Creating bounty: {request.title} by user {current_user['address']}")
# Create bounty in database
bounty = await bounty_service.create_bounty(
creator_id=current_user['address'],
**request.dict()
)
# Deploy bounty contract in background
background_tasks.add_task(
blockchain_service.deploy_bounty_contract,
bounty.bounty_id,
bounty.reward_amount,
bounty.tier,
bounty.deadline
)
return BountyResponse.from_orm(bounty)
except Exception as e:
logger.error(f"Failed to create bounty: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties", response_model=List[BountyResponse])
async def get_bounties(
filters: BountyFilterRequest = Depends(),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Get filtered list of bounties"""
try:
bounties = await bounty_service.get_bounties(
status=filters.status,
tier=filters.tier,
creator_id=filters.creator_id,
category=filters.category,
min_reward=filters.min_reward,
max_reward=filters.max_reward,
deadline_before=filters.deadline_before,
deadline_after=filters.deadline_after,
tags=filters.tags,
requires_zk_proof=filters.requires_zk_proof,
page=filters.page,
limit=filters.limit
)
return [BountyResponse.from_orm(bounty) for bounty in bounties]
except Exception as e:
logger.error(f"Failed to get bounties: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/{bounty_id}", response_model=BountyResponse)
async def get_bounty(
bounty_id: str,
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Get bounty details"""
try:
bounty = await bounty_service.get_bounty(bounty_id)
if not bounty:
raise HTTPException(status_code=404, detail="Bounty not found")
return BountyResponse.from_orm(bounty)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get bounty {bounty_id}: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/bounties/{bounty_id}/submit", response_model=BountySubmissionResponse)
async def submit_bounty_solution(
bounty_id: str,
request: BountySubmissionRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Submit a solution to a bounty"""
try:
logger.info(f"Submitting solution for bounty {bounty_id} by {current_user['address']}")
# Validate bounty exists and is active
bounty = await bounty_service.get_bounty(bounty_id)
if not bounty:
raise HTTPException(status_code=404, detail="Bounty not found")
if bounty.status != BountyStatus.ACTIVE:
raise HTTPException(status_code=400, detail="Bounty is not active")
if datetime.utcnow() > bounty.deadline:
raise HTTPException(status_code=400, detail="Bounty deadline has passed")
# Create submission
submission = await bounty_service.create_submission(
bounty_id=bounty_id,
submitter_address=current_user['address'],
**request.dict()
)
# Submit to blockchain in background
background_tasks.add_task(
blockchain_service.submit_bounty_solution,
bounty_id,
submission.submission_id,
request.zk_proof,
request.performance_hash,
request.accuracy,
request.response_time
)
return BountySubmissionResponse.from_orm(submission)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to submit bounty solution: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/{bounty_id}/submissions", response_model=List[BountySubmissionResponse])
async def get_bounty_submissions(
bounty_id: str,
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
current_user: dict = Depends(get_current_user)
):
"""Get all submissions for a bounty"""
try:
# Check if user is bounty creator or has permission
bounty = await bounty_service.get_bounty(bounty_id)
if not bounty:
raise HTTPException(status_code=404, detail="Bounty not found")
if bounty.creator_id != current_user['address']:
# Check if user has admin permissions
if not current_user.get('is_admin', False):
raise HTTPException(status_code=403, detail="Not authorized to view submissions")
submissions = await bounty_service.get_bounty_submissions(bounty_id)
return [BountySubmissionResponse.from_orm(sub) for sub in submissions]
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get bounty submissions: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/bounties/{bounty_id}/verify")
async def verify_bounty_submission(
bounty_id: str,
request: BountyVerificationRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Verify a bounty submission (oracle/admin only)"""
try:
# Check permissions
if not current_user.get('is_admin', False):
raise HTTPException(status_code=403, detail="Not authorized to verify submissions")
# Verify submission
await bounty_service.verify_submission(
bounty_id=bounty_id,
submission_id=request.submission_id,
verified=request.verified,
verifier_address=request.verifier_address,
verification_notes=request.verification_notes
)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.verify_submission,
bounty_id,
request.submission_id,
request.verified,
request.verifier_address
)
return {"message": "Submission verified successfully"}
except Exception as e:
logger.error(f"Failed to verify bounty submission: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/bounties/{bounty_id}/dispute")
async def dispute_bounty_submission(
bounty_id: str,
request: BountyDisputeRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Dispute a bounty submission"""
try:
# Create dispute
await bounty_service.create_dispute(
bounty_id=bounty_id,
submission_id=request.submission_id,
disputer_address=current_user['address'],
dispute_reason=request.dispute_reason
)
# Handle dispute on blockchain in background
background_tasks.add_task(
blockchain_service.dispute_submission,
bounty_id,
request.submission_id,
current_user['address'],
request.dispute_reason
)
return {"message": "Dispute created successfully"}
except Exception as e:
logger.error(f"Failed to create dispute: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/my/created", response_model=List[BountyResponse])
async def get_my_created_bounties(
status: Optional[BountyStatus] = None,
page: int = Field(default=1, ge=1),
limit: int = Field(default=20, ge=1, le=100),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
current_user: dict = Depends(get_current_user)
):
"""Get bounties created by the current user"""
try:
bounties = await bounty_service.get_user_created_bounties(
user_address=current_user['address'],
status=status,
page=page,
limit=limit
)
return [BountyResponse.from_orm(bounty) for bounty in bounties]
except Exception as e:
logger.error(f"Failed to get user created bounties: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/my/submissions", response_model=List[BountySubmissionResponse])
async def get_my_submissions(
status: Optional[SubmissionStatus] = None,
page: int = Field(default=1, ge=1),
limit: int = Field(default=20, ge=1, le=100),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
current_user: dict = Depends(get_current_user)
):
"""Get submissions made by the current user"""
try:
submissions = await bounty_service.get_user_submissions(
user_address=current_user['address'],
status=status,
page=page,
limit=limit
)
return [BountySubmissionResponse.from_orm(sub) for sub in submissions]
except Exception as e:
logger.error(f"Failed to get user submissions: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/leaderboard")
async def get_bounty_leaderboard(
period: str = Field(default="weekly", regex="^(daily|weekly|monthly)$"),
limit: int = Field(default=50, ge=1, le=100),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Get bounty leaderboard"""
try:
leaderboard = await bounty_service.get_leaderboard(
period=period,
limit=limit
)
return leaderboard
except Exception as e:
logger.error(f"Failed to get bounty leaderboard: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/stats", response_model=BountyStatsResponse)
async def get_bounty_stats(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Get bounty statistics"""
try:
stats = await bounty_service.get_bounty_stats(period=period)
return BountyStatsResponse.from_orm(stats)
except Exception as e:
logger.error(f"Failed to get bounty stats: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/bounties/{bounty_id}/expire")
async def expire_bounty(
bounty_id: str,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Expire a bounty (creator only)"""
try:
# Check if user is bounty creator
bounty = await bounty_service.get_bounty(bounty_id)
if not bounty:
raise HTTPException(status_code=404, detail="Bounty not found")
if bounty.creator_id != current_user['address']:
raise HTTPException(status_code=403, detail="Not authorized to expire bounty")
if bounty.status != BountyStatus.ACTIVE:
raise HTTPException(status_code=400, detail="Bounty is not active")
if datetime.utcnow() <= bounty.deadline:
raise HTTPException(status_code=400, detail="Bounty deadline has not passed")
# Expire bounty
await bounty_service.expire_bounty(bounty_id)
# Handle on blockchain in background
background_tasks.add_task(
blockchain_service.expire_bounty,
bounty_id
)
return {"message": "Bounty expired successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to expire bounty: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/categories")
async def get_bounty_categories(
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Get all bounty categories"""
try:
categories = await bounty_service.get_categories()
return {"categories": categories}
except Exception as e:
logger.error(f"Failed to get bounty categories: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/tags")
async def get_bounty_tags(
limit: int = Field(default=100, ge=1, le=500),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Get popular bounty tags"""
try:
tags = await bounty_service.get_popular_tags(limit=limit)
return {"tags": tags}
except Exception as e:
logger.error(f"Failed to get bounty tags: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/bounties/search")
async def search_bounties(
query: str = Field(..., min_length=1, max_length=100),
page: int = Field(default=1, ge=1),
limit: int = Field(default=20, ge=1, le=100),
session: SessionDep = Depends(),
bounty_service: BountyService = Depends(get_bounty_service)
):
"""Search bounties by text"""
try:
bounties = await bounty_service.search_bounties(
query=query,
page=page,
limit=limit
)
return [BountyResponse.from_orm(bounty) for bounty in bounties]
except Exception as e:
logger.error(f"Failed to search bounties: {e}")
raise HTTPException(status_code=400, detail=str(e))

View File

@@ -0,0 +1,449 @@
"""
Ecosystem Metrics Dashboard API
REST API for developer ecosystem metrics and analytics
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from pydantic import BaseModel, Field
from ..storage import SessionDep
from ..logging import get_logger
from ..domain.bounty import EcosystemMetrics, BountyStats, AgentMetrics
from ..services.ecosystem_service import EcosystemService
from ..auth import get_current_user
logger = get_logger(__name__)
router = APIRouter()
# Pydantic models for request/response
class DeveloperEarningsResponse(BaseModel):
period: str
total_earnings: float
average_earnings: float
top_earners: List[Dict[str, Any]]
earnings_growth: float
active_developers: int
class AgentUtilizationResponse(BaseModel):
period: str
total_agents: int
active_agents: int
utilization_rate: float
top_utilized_agents: List[Dict[str, Any]]
average_performance: float
performance_distribution: Dict[str, int]
class TreasuryAllocationResponse(BaseModel):
period: str
treasury_balance: float
total_inflow: float
total_outflow: float
dao_revenue: float
allocation_breakdown: Dict[str, float]
burn_rate: float
class StakingMetricsResponse(BaseModel):
period: str
total_staked: float
total_stakers: int
average_apy: float
staking_rewards_total: float
top_staking_pools: List[Dict[str, Any]]
tier_distribution: Dict[str, int]
class BountyAnalyticsResponse(BaseModel):
period: str
active_bounties: int
completion_rate: float
average_reward: float
total_volume: float
category_distribution: Dict[str, int]
difficulty_distribution: Dict[str, int]
class EcosystemOverviewResponse(BaseModel):
timestamp: datetime
period_type: str
developer_earnings: DeveloperEarningsResponse
agent_utilization: AgentUtilizationResponse
treasury_allocation: TreasuryAllocationResponse
staking_metrics: StakingMetricsResponse
bounty_analytics: BountyAnalyticsResponse
health_score: float
growth_indicators: Dict[str, float]
class MetricsFilterRequest(BaseModel):
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$")
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
compare_period: Optional[str] = None
# Dependency injection
def get_ecosystem_service(session: SessionDep) -> EcosystemService:
return EcosystemService(session)
# API endpoints
@router.get("/ecosystem/developer-earnings", response_model=DeveloperEarningsResponse)
async def get_developer_earnings(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service),
current_user: dict = Depends(get_current_user)
):
"""Get developer earnings metrics"""
try:
earnings_data = await ecosystem_service.get_developer_earnings(period=period)
return DeveloperEarningsResponse(
period=period,
**earnings_data
)
except Exception as e:
logger.error(f"Failed to get developer earnings: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/agent-utilization", response_model=AgentUtilizationResponse)
async def get_agent_utilization(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get agent utilization metrics"""
try:
utilization_data = await ecosystem_service.get_agent_utilization(period=period)
return AgentUtilizationResponse(
period=period,
**utilization_data
)
except Exception as e:
logger.error(f"Failed to get agent utilization: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/treasury-allocation", response_model=TreasuryAllocationResponse)
async def get_treasury_allocation(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get DAO treasury allocation metrics"""
try:
treasury_data = await ecosystem_service.get_treasury_allocation(period=period)
return TreasuryAllocationResponse(
period=period,
**treasury_data
)
except Exception as e:
logger.error(f"Failed to get treasury allocation: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/staking-metrics", response_model=StakingMetricsResponse)
async def get_staking_metrics(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get staking system metrics"""
try:
staking_data = await ecosystem_service.get_staking_metrics(period=period)
return StakingMetricsResponse(
period=period,
**staking_data
)
except Exception as e:
logger.error(f"Failed to get staking metrics: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/bounty-analytics", response_model=BountyAnalyticsResponse)
async def get_bounty_analytics(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get bounty system analytics"""
try:
bounty_data = await ecosystem_service.get_bounty_analytics(period=period)
return BountyAnalyticsResponse(
period=period,
**bounty_data
)
except Exception as e:
logger.error(f"Failed to get bounty analytics: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/overview", response_model=EcosystemOverviewResponse)
async def get_ecosystem_overview(
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get comprehensive ecosystem overview"""
try:
overview_data = await ecosystem_service.get_ecosystem_overview(period_type=period_type)
return EcosystemOverviewResponse(
timestamp=overview_data["timestamp"],
period_type=period_type,
developer_earnings=DeveloperEarningsResponse(**overview_data["developer_earnings"]),
agent_utilization=AgentUtilizationResponse(**overview_data["agent_utilization"]),
treasury_allocation=TreasuryAllocationResponse(**overview_data["treasury_allocation"]),
staking_metrics=StakingMetricsResponse(**overview_data["staking_metrics"]),
bounty_analytics=BountyAnalyticsResponse(**overview_data["bounty_analytics"]),
health_score=overview_data["health_score"],
growth_indicators=overview_data["growth_indicators"]
)
except Exception as e:
logger.error(f"Failed to get ecosystem overview: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/metrics")
async def get_ecosystem_metrics(
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
limit: int = Field(default=100, ge=1, le=1000),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get time-series ecosystem metrics"""
try:
metrics = await ecosystem_service.get_time_series_metrics(
period_type=period_type,
start_date=start_date,
end_date=end_date,
limit=limit
)
return {
"metrics": metrics,
"period_type": period_type,
"count": len(metrics)
}
except Exception as e:
logger.error(f"Failed to get ecosystem metrics: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/health-score")
async def get_ecosystem_health_score(
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get overall ecosystem health score"""
try:
health_score = await ecosystem_service.calculate_health_score()
return {
"health_score": health_score["score"],
"components": health_score["components"],
"recommendations": health_score["recommendations"],
"last_updated": health_score["last_updated"]
}
except Exception as e:
logger.error(f"Failed to get health score: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/growth-indicators")
async def get_growth_indicators(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get ecosystem growth indicators"""
try:
growth_data = await ecosystem_service.get_growth_indicators(period=period)
return {
"period": period,
"indicators": growth_data,
"trend": growth_data.get("trend", "stable"),
"growth_rate": growth_data.get("growth_rate", 0.0)
}
except Exception as e:
logger.error(f"Failed to get growth indicators: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/top-performers")
async def get_top_performers(
category: str = Field(default="all", regex="^(developers|agents|stakers|all)$"),
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
limit: int = Field(default=50, ge=1, le=100),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get top performers in different categories"""
try:
performers = await ecosystem_service.get_top_performers(
category=category,
period=period,
limit=limit
)
return {
"category": category,
"period": period,
"performers": performers,
"count": len(performers)
}
except Exception as e:
logger.error(f"Failed to get top performers: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/predictions")
async def get_ecosystem_predictions(
metric: str = Field(default="all", regex="^(earnings|staking|bounties|agents|all)$"),
horizon: int = Field(default=30, ge=1, le=365), # days
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get ecosystem predictions based on historical data"""
try:
predictions = await ecosystem_service.get_predictions(
metric=metric,
horizon=horizon
)
return {
"metric": metric,
"horizon_days": horizon,
"predictions": predictions,
"confidence": predictions.get("confidence", 0.0),
"model_used": predictions.get("model", "linear_regression")
}
except Exception as e:
logger.error(f"Failed to get predictions: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/alerts")
async def get_ecosystem_alerts(
severity: str = Field(default="all", regex="^(low|medium|high|critical|all)$"),
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get ecosystem alerts and anomalies"""
try:
alerts = await ecosystem_service.get_alerts(severity=severity)
return {
"alerts": alerts,
"severity": severity,
"count": len(alerts),
"last_updated": datetime.utcnow()
}
except Exception as e:
logger.error(f"Failed to get alerts: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/comparison")
async def get_ecosystem_comparison(
current_period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
compare_period: str = Field(default="previous", regex="^(previous|same_last_year|custom)$"),
custom_start_date: Optional[datetime] = None,
custom_end_date: Optional[datetime] = None,
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Compare ecosystem metrics between periods"""
try:
comparison = await ecosystem_service.get_period_comparison(
current_period=current_period,
compare_period=compare_period,
custom_start_date=custom_start_date,
custom_end_date=custom_end_date
)
return {
"current_period": current_period,
"compare_period": compare_period,
"comparison": comparison,
"summary": comparison.get("summary", {})
}
except Exception as e:
logger.error(f"Failed to get comparison: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/export")
async def export_ecosystem_data(
format: str = Field(default="json", regex="^(json|csv|xlsx)$"),
period_type: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Export ecosystem data in various formats"""
try:
export_data = await ecosystem_service.export_data(
format=format,
period_type=period_type,
start_date=start_date,
end_date=end_date
)
return {
"format": format,
"period_type": period_type,
"data_url": export_data["url"],
"file_size": export_data.get("file_size", 0),
"expires_at": export_data.get("expires_at"),
"record_count": export_data.get("record_count", 0)
}
except Exception as e:
logger.error(f"Failed to export data: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/real-time")
async def get_real_time_metrics(
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get real-time ecosystem metrics"""
try:
real_time_data = await ecosystem_service.get_real_time_metrics()
return {
"timestamp": datetime.utcnow(),
"metrics": real_time_data,
"update_frequency": "60s" # Update frequency in seconds
}
except Exception as e:
logger.error(f"Failed to get real-time metrics: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/ecosystem/kpi-dashboard")
async def get_kpi_dashboard(
session: SessionDep = Depends(),
ecosystem_service: EcosystemService = Depends(get_ecosystem_service)
):
"""Get KPI dashboard with key performance indicators"""
try:
kpi_data = await ecosystem_service.get_kpi_dashboard()
return {
"kpis": kpi_data,
"last_updated": datetime.utcnow(),
"refresh_interval": 300 # 5 minutes
}
except Exception as e:
logger.error(f"Failed to get KPI dashboard: {e}")
raise HTTPException(status_code=400, detail=str(e))

View File

@@ -0,0 +1,723 @@
"""
Staking Management API
REST API for AI agent staking system with reputation-based yield farming
"""
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from pydantic import BaseModel, Field, validator
from ..storage import SessionDep
from ..logging import get_logger
from ..domain.bounty import (
AgentStake, AgentMetrics, StakingPool, StakeStatus,
PerformanceTier, EcosystemMetrics
)
from ..services.staking_service import StakingService
from ..services.blockchain_service import BlockchainService
from ..auth import get_current_user
logger = get_logger(__name__)
router = APIRouter()
# Pydantic models for request/response
class StakeCreateRequest(BaseModel):
agent_wallet: str = Field(..., min_length=1)
amount: float = Field(..., gt=0)
lock_period: int = Field(default=30, ge=1, le=365) # days
auto_compound: bool = Field(default=False)
@validator('amount')
def validate_amount(cls, v):
if v < 100.0:
raise ValueError('Minimum stake amount is 100 AITBC')
if v > 100000.0:
raise ValueError('Maximum stake amount is 100,000 AITBC')
return v
class StakeResponse(BaseModel):
stake_id: str
staker_address: str
agent_wallet: str
amount: float
lock_period: int
start_time: datetime
end_time: datetime
status: StakeStatus
accumulated_rewards: float
last_reward_time: datetime
current_apy: float
agent_tier: PerformanceTier
performance_multiplier: float
auto_compound: bool
unbonding_time: Optional[datetime]
early_unbond_penalty: float
lock_bonus_multiplier: float
stake_data: Dict[str, Any]
class StakeUpdateRequest(BaseModel):
additional_amount: float = Field(..., gt=0)
class StakeUnbondRequest(BaseModel):
stake_id: str = Field(..., min_length=1)
class StakeCompleteRequest(BaseModel):
stake_id: str = Field(..., min_length=1)
class AgentMetricsResponse(BaseModel):
agent_wallet: str
total_staked: float
staker_count: int
total_rewards_distributed: float
average_accuracy: float
total_submissions: int
successful_submissions: int
success_rate: float
current_tier: PerformanceTier
tier_score: float
reputation_score: float
last_update_time: datetime
first_submission_time: Optional[datetime]
average_response_time: Optional[float]
total_compute_time: Optional[float]
energy_efficiency_score: Optional[float]
weekly_accuracy: List[float]
monthly_earnings: List[float]
agent_metadata: Dict[str, Any]
class StakingPoolResponse(BaseModel):
agent_wallet: str
total_staked: float
total_rewards: float
pool_apy: float
staker_count: int
active_stakers: List[str]
last_distribution_time: datetime
distribution_frequency: int
min_stake_amount: float
max_stake_amount: float
auto_compound_enabled: bool
pool_performance_score: float
volatility_score: float
pool_metadata: Dict[str, Any]
class StakingFilterRequest(BaseModel):
agent_wallet: Optional[str] = None
status: Optional[StakeStatus] = None
min_amount: Optional[float] = Field(default=None, ge=0)
max_amount: Optional[float] = Field(default=None, ge=0)
agent_tier: Optional[PerformanceTier] = None
auto_compound: Optional[bool] = None
page: int = Field(default=1, ge=1)
limit: int = Field(default=20, ge=1, le=100)
class StakingStatsResponse(BaseModel):
total_staked: float
total_stakers: int
active_stakes: int
average_apy: float
total_rewards_distributed: float
top_agents: List[Dict[str, Any]]
tier_distribution: Dict[str, int]
lock_period_distribution: Dict[str, int]
class AgentPerformanceUpdateRequest(BaseModel):
agent_wallet: str = Field(..., min_length=1)
accuracy: float = Field(..., ge=0, le=100)
successful: bool = Field(default=True)
response_time: Optional[float] = Field(default=None, gt=0)
compute_power: Optional[float] = Field(default=None, gt=0)
energy_efficiency: Optional[float] = Field(default=None, ge=0, le=100)
class EarningsDistributionRequest(BaseModel):
agent_wallet: str = Field(..., min_length=1)
total_earnings: float = Field(..., gt=0)
distribution_data: Dict[str, Any] = Field(default_factory=dict)
# Dependency injection
def get_staking_service(session: SessionDep) -> StakingService:
return StakingService(session)
def get_blockchain_service() -> BlockchainService:
return BlockchainService()
# API endpoints
@router.post("/stake", response_model=StakeResponse)
async def create_stake(
request: StakeCreateRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Create a new stake on an agent wallet"""
try:
logger.info(f"Creating stake: {request.amount} AITBC on {request.agent_wallet} by {current_user['address']}")
# Validate agent is supported
agent_metrics = await staking_service.get_agent_metrics(request.agent_wallet)
if not agent_metrics:
raise HTTPException(status_code=404, detail="Agent not supported for staking")
# Create stake in database
stake = await staking_service.create_stake(
staker_address=current_user['address'],
**request.dict()
)
# Deploy stake contract in background
background_tasks.add_task(
blockchain_service.create_stake_contract,
stake.stake_id,
request.agent_wallet,
request.amount,
request.lock_period,
request.auto_compound
)
return StakeResponse.from_orm(stake)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to create stake: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/stake/{stake_id}", response_model=StakeResponse)
async def get_stake(
stake_id: str,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
current_user: dict = Depends(get_current_user)
):
"""Get stake details"""
try:
stake = await staking_service.get_stake(stake_id)
if not stake:
raise HTTPException(status_code=404, detail="Stake not found")
# Check ownership
if stake.staker_address != current_user['address']:
raise HTTPException(status_code=403, detail="Not authorized to view this stake")
return StakeResponse.from_orm(stake)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get stake {stake_id}: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/stakes", response_model=List[StakeResponse])
async def get_stakes(
filters: StakingFilterRequest = Depends(),
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
current_user: dict = Depends(get_current_user)
):
"""Get filtered list of user's stakes"""
try:
stakes = await staking_service.get_user_stakes(
user_address=current_user['address'],
agent_wallet=filters.agent_wallet,
status=filters.status,
min_amount=filters.min_amount,
max_amount=filters.max_amount,
agent_tier=filters.agent_tier,
auto_compound=filters.auto_compound,
page=filters.page,
limit=filters.limit
)
return [StakeResponse.from_orm(stake) for stake in stakes]
except Exception as e:
logger.error(f"Failed to get stakes: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/stake/{stake_id}/add", response_model=StakeResponse)
async def add_to_stake(
stake_id: str,
request: StakeUpdateRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Add more tokens to an existing stake"""
try:
# Get stake and verify ownership
stake = await staking_service.get_stake(stake_id)
if not stake:
raise HTTPException(status_code=404, detail="Stake not found")
if stake.staker_address != current_user['address']:
raise HTTPException(status_code=403, detail="Not authorized to modify this stake")
if stake.status != StakeStatus.ACTIVE:
raise HTTPException(status_code=400, detail="Stake is not active")
# Update stake
updated_stake = await staking_service.add_to_stake(
stake_id=stake_id,
additional_amount=request.additional_amount
)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.add_to_stake,
stake_id,
request.additional_amount
)
return StakeResponse.from_orm(updated_stake)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to add to stake: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/stake/{stake_id}/unbond")
async def unbond_stake(
stake_id: str,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Initiate unbonding for a stake"""
try:
# Get stake and verify ownership
stake = await staking_service.get_stake(stake_id)
if not stake:
raise HTTPException(status_code=404, detail="Stake not found")
if stake.staker_address != current_user['address']:
raise HTTPException(status_code=403, detail="Not authorized to unbond this stake")
if stake.status != StakeStatus.ACTIVE:
raise HTTPException(status_code=400, detail="Stake is not active")
if datetime.utcnow() < stake.end_time:
raise HTTPException(status_code=400, detail="Lock period has not ended")
# Initiate unbonding
await staking_service.unbond_stake(stake_id)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.unbond_stake,
stake_id
)
return {"message": "Unbonding initiated successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to unbond stake: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/stake/{stake_id}/complete")
async def complete_unbonding(
stake_id: str,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Complete unbonding and return stake + rewards"""
try:
# Get stake and verify ownership
stake = await staking_service.get_stake(stake_id)
if not stake:
raise HTTPException(status_code=404, detail="Stake not found")
if stake.staker_address != current_user['address']:
raise HTTPException(status_code=403, detail="Not authorized to complete this stake")
if stake.status != StakeStatus.UNBONDING:
raise HTTPException(status_code=400, detail="Stake is not unbonding")
# Complete unbonding
result = await staking_service.complete_unbonding(stake_id)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.complete_unbonding,
stake_id
)
return {
"message": "Unbonding completed successfully",
"total_amount": result["total_amount"],
"total_rewards": result["total_rewards"],
"penalty": result.get("penalty", 0.0)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to complete unbonding: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/stake/{stake_id}/rewards")
async def get_stake_rewards(
stake_id: str,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
current_user: dict = Depends(get_current_user)
):
"""Get current rewards for a stake"""
try:
# Get stake and verify ownership
stake = await staking_service.get_stake(stake_id)
if not stake:
raise HTTPException(status_code=404, detail="Stake not found")
if stake.staker_address != current_user['address']:
raise HTTPException(status_code=403, detail="Not authorized to view this stake")
# Calculate rewards
rewards = await staking_service.calculate_rewards(stake_id)
return {
"stake_id": stake_id,
"accumulated_rewards": stake.accumulated_rewards,
"current_rewards": rewards,
"total_rewards": stake.accumulated_rewards + rewards,
"current_apy": stake.current_apy,
"last_reward_time": stake.last_reward_time
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get stake rewards: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/agents/{agent_wallet}/metrics", response_model=AgentMetricsResponse)
async def get_agent_metrics(
agent_wallet: str,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get agent performance metrics"""
try:
metrics = await staking_service.get_agent_metrics(agent_wallet)
if not metrics:
raise HTTPException(status_code=404, detail="Agent not found")
return AgentMetricsResponse.from_orm(metrics)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get agent metrics: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/agents/{agent_wallet}/staking-pool", response_model=StakingPoolResponse)
async def get_staking_pool(
agent_wallet: str,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get staking pool information for an agent"""
try:
pool = await staking_service.get_staking_pool(agent_wallet)
if not pool:
raise HTTPException(status_code=404, detail="Staking pool not found")
return StakingPoolResponse.from_orm(pool)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get staking pool: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/agents/{agent_wallet}/apy")
async def get_agent_apy(
agent_wallet: str,
lock_period: int = Field(default=30, ge=1, le=365),
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get current APY for staking on an agent"""
try:
apy = await staking_service.calculate_apy(agent_wallet, lock_period)
return {
"agent_wallet": agent_wallet,
"lock_period": lock_period,
"current_apy": apy,
"base_apy": 5.0, # Base APY
"tier_multiplier": apy / 5.0 if apy > 0 else 1.0
}
except Exception as e:
logger.error(f"Failed to get agent APY: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/agents/{agent_wallet}/performance")
async def update_agent_performance(
agent_wallet: str,
request: AgentPerformanceUpdateRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Update agent performance metrics (oracle only)"""
try:
# Check permissions
if not current_user.get('is_oracle', False):
raise HTTPException(status_code=403, detail="Not authorized to update performance")
# Update performance
await staking_service.update_agent_performance(
agent_wallet=agent_wallet,
**request.dict()
)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.update_agent_performance,
agent_wallet,
request.accuracy,
request.successful
)
return {"message": "Agent performance updated successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to update agent performance: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/agents/{agent_wallet}/distribute-earnings")
async def distribute_agent_earnings(
agent_wallet: str,
request: EarningsDistributionRequest,
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Distribute agent earnings to stakers"""
try:
# Check permissions
if not current_user.get('is_admin', False):
raise HTTPException(status_code=403, detail="Not authorized to distribute earnings")
# Distribute earnings
result = await staking_service.distribute_earnings(
agent_wallet=agent_wallet,
total_earnings=request.total_earnings,
distribution_data=request.distribution_data
)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.distribute_earnings,
agent_wallet,
request.total_earnings
)
return {
"message": "Earnings distributed successfully",
"total_distributed": result["total_distributed"],
"staker_count": result["staker_count"],
"platform_fee": result.get("platform_fee", 0.0)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to distribute earnings: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/agents/supported")
async def get_supported_agents(
page: int = Field(default=1, ge=1),
limit: int = Field(default=50, ge=1, le=100),
tier: Optional[PerformanceTier] = None,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get list of supported agents for staking"""
try:
agents = await staking_service.get_supported_agents(
page=page,
limit=limit,
tier=tier
)
return {
"agents": agents,
"total_count": len(agents),
"page": page,
"limit": limit
}
except Exception as e:
logger.error(f"Failed to get supported agents: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/staking/stats", response_model=StakingStatsResponse)
async def get_staking_stats(
period: str = Field(default="daily", regex="^(hourly|daily|weekly|monthly)$"),
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get staking system statistics"""
try:
stats = await staking_service.get_staking_stats(period=period)
return StakingStatsResponse.from_orm(stats)
except Exception as e:
logger.error(f"Failed to get staking stats: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/staking/leaderboard")
async def get_staking_leaderboard(
period: str = Field(default="weekly", regex="^(daily|weekly|monthly)$"),
metric: str = Field(default="total_staked", regex="^(total_staked|total_rewards|apy)$"),
limit: int = Field(default=50, ge=1, le=100),
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get staking leaderboard"""
try:
leaderboard = await staking_service.get_leaderboard(
period=period,
metric=metric,
limit=limit
)
return leaderboard
except Exception as e:
logger.error(f"Failed to get staking leaderboard: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/staking/my-positions", response_model=List[StakeResponse])
async def get_my_staking_positions(
status: Optional[StakeStatus] = None,
agent_wallet: Optional[str] = None,
page: int = Field(default=1, ge=1),
limit: int = Field(default=20, ge=1, le=100),
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
current_user: dict = Depends(get_current_user)
):
"""Get current user's staking positions"""
try:
stakes = await staking_service.get_user_stakes(
user_address=current_user['address'],
status=status,
agent_wallet=agent_wallet,
page=page,
limit=limit
)
return [StakeResponse.from_orm(stake) for stake in stakes]
except Exception as e:
logger.error(f"Failed to get staking positions: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/staking/my-rewards")
async def get_my_staking_rewards(
period: str = Field(default="monthly", regex="^(daily|weekly|monthly)$"),
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
current_user: dict = Depends(get_current_user)
):
"""Get current user's staking rewards"""
try:
rewards = await staking_service.get_user_rewards(
user_address=current_user['address'],
period=period
)
return rewards
except Exception as e:
logger.error(f"Failed to get staking rewards: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.post("/staking/claim-rewards")
async def claim_staking_rewards(
stake_ids: List[str],
background_tasks: BackgroundTasks,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service),
blockchain_service: BlockchainService = Depends(get_blockchain_service),
current_user: dict = Depends(get_current_user)
):
"""Claim accumulated rewards for multiple stakes"""
try:
# Verify ownership of all stakes
total_rewards = 0.0
for stake_id in stake_ids:
stake = await staking_service.get_stake(stake_id)
if not stake:
raise HTTPException(status_code=404, detail=f"Stake {stake_id} not found")
if stake.staker_address != current_user['address']:
raise HTTPException(status_code=403, detail=f"Not authorized to claim rewards for stake {stake_id}")
total_rewards += stake.accumulated_rewards
if total_rewards <= 0:
raise HTTPException(status_code=400, detail="No rewards to claim")
# Claim rewards
result = await staking_service.claim_rewards(stake_ids)
# Update blockchain in background
background_tasks.add_task(
blockchain_service.claim_rewards,
stake_ids
)
return {
"message": "Rewards claimed successfully",
"total_rewards": total_rewards,
"claimed_stakes": len(stake_ids),
"transaction_hash": result.get("transaction_hash")
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to claim rewards: {e}")
raise HTTPException(status_code=400, detail=str(e))
@router.get("/staking/risk-assessment/{agent_wallet}")
async def get_risk_assessment(
agent_wallet: str,
session: SessionDep = Depends(),
staking_service: StakingService = Depends(get_staking_service)
):
"""Get risk assessment for staking on an agent"""
try:
assessment = await staking_service.get_risk_assessment(agent_wallet)
return assessment
except Exception as e:
logger.error(f"Failed to get risk assessment: {e}")
raise HTTPException(status_code=400, detail=str(e))