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:
584
apps/coordinator-api/src/app/routers/bounty.py
Normal file
584
apps/coordinator-api/src/app/routers/bounty.py
Normal 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))
|
||||
449
apps/coordinator-api/src/app/routers/ecosystem_dashboard.py
Normal file
449
apps/coordinator-api/src/app/routers/ecosystem_dashboard.py
Normal 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))
|
||||
723
apps/coordinator-api/src/app/routers/staking.py
Normal file
723
apps/coordinator-api/src/app/routers/staking.py
Normal 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))
|
||||
Reference in New Issue
Block a user