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,439 @@
"""
Bounty System Domain Models
Database models for AI agent bounty system with ZK-proof verification
"""
from typing import Optional, List, Dict, Any
from sqlmodel import Field, SQLModel, Column, JSON, Relationship
from datetime import datetime
from enum import Enum
import uuid
class BountyStatus(str, Enum):
CREATED = "created"
ACTIVE = "active"
SUBMITTED = "submitted"
VERIFIED = "verified"
COMPLETED = "completed"
EXPIRED = "expired"
DISPUTED = "disputed"
class BountyTier(str, Enum):
BRONZE = "bronze"
SILVER = "silver"
GOLD = "gold"
PLATINUM = "platinum"
class SubmissionStatus(str, Enum):
PENDING = "pending"
VERIFIED = "verified"
REJECTED = "rejected"
DISPUTED = "disputed"
class StakeStatus(str, Enum):
ACTIVE = "active"
UNBONDING = "unbonding"
COMPLETED = "completed"
SLASHED = "slashed"
class PerformanceTier(str, Enum):
BRONZE = "bronze"
SILVER = "silver"
GOLD = "gold"
PLATINUM = "platinum"
DIAMOND = "diamond"
class Bounty(SQLModel, table=True):
"""AI agent bounty with ZK-proof verification requirements"""
__tablename__ = "bounties"
bounty_id: str = Field(primary_key=True, default_factory=lambda: f"bounty_{uuid.uuid4().hex[:8]}")
title: str = Field(index=True)
description: str = Field(index=True)
reward_amount: float = Field(index=True)
creator_id: str = Field(index=True)
tier: BountyTier = Field(default=BountyTier.BRONZE)
status: BountyStatus = Field(default=BountyStatus.CREATED)
# Performance requirements
performance_criteria: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
min_accuracy: float = Field(default=90.0)
max_response_time: Optional[int] = Field(default=None) # milliseconds
# Timing
deadline: datetime = Field(index=True)
creation_time: datetime = Field(default_factory=datetime.utcnow)
# Limits
max_submissions: int = Field(default=100)
submission_count: int = Field(default=0)
# Configuration
requires_zk_proof: bool = Field(default=True)
auto_verify_threshold: float = Field(default=95.0)
# Winner information
winning_submission_id: Optional[str] = Field(default=None)
winner_address: Optional[str] = Field(default=None)
# Fees
creation_fee: float = Field(default=0.0)
success_fee: float = Field(default=0.0)
platform_fee: float = Field(default=0.0)
# Metadata
tags: List[str] = Field(default_factory=list, sa_column=Column(JSON))
category: Optional[str] = Field(default=None)
difficulty: Optional[str] = Field(default=None)
# Relationships
submissions: List["BountySubmission"] = Relationship(back_populates="bounty")
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_bounty_status_deadline", "columns": ["status", "deadline"]},
{"name": "ix_bounty_creator_status", "columns": ["creator_id", "status"]},
{"name": "ix_bounty_tier_reward", "columns": ["tier", "reward_amount"]},
]}
)
class BountySubmission(SQLModel, table=True):
"""Submission for a bounty with ZK-proof and performance metrics"""
__tablename__ = "bounty_submissions"
submission_id: str = Field(primary_key=True, default_factory=lambda: f"sub_{uuid.uuid4().hex[:8]}")
bounty_id: str = Field(foreign_key="bounties.bounty_id", index=True)
submitter_address: str = Field(index=True)
# Performance metrics
accuracy: float = Field(index=True)
response_time: Optional[int] = Field(default=None) # milliseconds
compute_power: Optional[float] = Field(default=None)
energy_efficiency: Optional[float] = Field(default=None)
# ZK-proof data
zk_proof: Optional[Dict[str, Any]] = Field(default_factory=dict, sa_column=Column(JSON))
performance_hash: str = Field(index=True)
# Status and verification
status: SubmissionStatus = Field(default=SubmissionStatus.PENDING)
verification_time: Optional[datetime] = Field(default=None)
verifier_address: Optional[str] = Field(default=None)
# Dispute information
dispute_reason: Optional[str] = Field(default=None)
dispute_time: Optional[datetime] = Field(default=None)
dispute_resolved: bool = Field(default=False)
# Timing
submission_time: datetime = Field(default_factory=datetime.utcnow)
# Metadata
submission_data: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
test_results: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Relationships
bounty: Bounty = Relationship(back_populates="submissions")
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_submission_bounty_status", "columns": ["bounty_id", "status"]},
{"name": "ix_submission_submitter_time", "columns": ["submitter_address", "submission_time"]},
{"name": "ix_submission_accuracy", "columns": ["accuracy"]},
]}
)
class AgentStake(SQLModel, table=True):
"""Staking position on an AI agent wallet"""
__tablename__ = "agent_stakes"
stake_id: str = Field(primary_key=True, default_factory=lambda: f"stake_{uuid.uuid4().hex[:8]}")
staker_address: str = Field(index=True)
agent_wallet: str = Field(index=True)
# Stake details
amount: float = Field(index=True)
lock_period: int = Field(default=30) # days
start_time: datetime = Field(default_factory=datetime.utcnow)
end_time: datetime
# Status and rewards
status: StakeStatus = Field(default=StakeStatus.ACTIVE)
accumulated_rewards: float = Field(default=0.0)
last_reward_time: datetime = Field(default_factory=datetime.utcnow)
# APY and performance
current_apy: float = Field(default=5.0) # percentage
agent_tier: PerformanceTier = Field(default=PerformanceTier.BRONZE)
performance_multiplier: float = Field(default=1.0)
# Configuration
auto_compound: bool = Field(default=False)
unbonding_time: Optional[datetime] = Field(default=None)
# Penalties and bonuses
early_unbond_penalty: float = Field(default=0.0)
lock_bonus_multiplier: float = Field(default=1.0)
# Metadata
stake_data: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_stake_agent_status", "columns": ["agent_wallet", "status"]},
{"name": "ix_stake_staker_status", "columns": ["staker_address", "status"]},
{"name": "ix_stake_amount_apy", "columns": ["amount", "current_apy"]},
]}
)
class AgentMetrics(SQLModel, table=True):
"""Performance metrics for AI agents"""
__tablename__ = "agent_metrics"
agent_wallet: str = Field(primary_key=True, index=True)
# Staking metrics
total_staked: float = Field(default=0.0)
staker_count: int = Field(default=0)
total_rewards_distributed: float = Field(default=0.0)
# Performance metrics
average_accuracy: float = Field(default=0.0)
total_submissions: int = Field(default=0)
successful_submissions: int = Field(default=0)
success_rate: float = Field(default=0.0)
# Tier and scoring
current_tier: PerformanceTier = Field(default=PerformanceTier.BRONZE)
tier_score: float = Field(default=60.0)
reputation_score: float = Field(default=0.0)
# Timing
last_update_time: datetime = Field(default_factory=datetime.utcnow)
first_submission_time: Optional[datetime] = Field(default=None)
# Additional metrics
average_response_time: Optional[float] = Field(default=None)
total_compute_time: Optional[float] = Field(default=None)
energy_efficiency_score: Optional[float] = Field(default=None)
# Historical data
weekly_accuracy: List[float] = Field(default_factory=list, sa_column=Column(JSON))
monthly_earnings: List[float] = Field(default_factory=list, sa_column=Column(JSON))
# Metadata
agent_metadata: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Relationships
stakes: List[AgentStake] = Relationship(back_populates="agent_metrics")
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_metrics_tier_score", "columns": ["current_tier", "tier_score"]},
{"name": "ix_metrics_staked", "columns": ["total_staked"]},
{"name": "ix_metrics_accuracy", "columns": ["average_accuracy"]},
]}
)
class StakingPool(SQLModel, table=True):
"""Staking pool for an agent"""
__tablename__ = "staking_pools"
agent_wallet: str = Field(primary_key=True, index=True)
# Pool metrics
total_staked: float = Field(default=0.0)
total_rewards: float = Field(default=0.0)
pool_apy: float = Field(default=5.0)
# Staker information
staker_count: int = Field(default=0)
active_stakers: List[str] = Field(default_factory=list, sa_column=Column(JSON))
# Distribution
last_distribution_time: datetime = Field(default_factory=datetime.utcnow)
distribution_frequency: int = Field(default=1) # days
# Pool configuration
min_stake_amount: float = Field(default=100.0)
max_stake_amount: float = Field(default=100000.0)
auto_compound_enabled: bool = Field(default=False)
# Performance tracking
pool_performance_score: float = Field(default=0.0)
volatility_score: float = Field(default=0.0)
# Metadata
pool_metadata: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_pool_apy_staked", "columns": ["pool_apy", "total_staked"]},
{"name": "ix_pool_performance", "columns": ["pool_performance_score"]},
]}
)
class BountyIntegration(SQLModel, table=True):
"""Integration between performance verification and bounty completion"""
__tablename__ = "bounty_integrations"
integration_id: str = Field(primary_key=True, default_factory=lambda: f"int_{uuid.uuid4().hex[:8]}")
# Mapping information
performance_hash: str = Field(index=True)
bounty_id: str = Field(foreign_key="bounties.bounty_id", index=True)
submission_id: str = Field(foreign_key="bounty_submissions.submission_id", index=True)
# Status and timing
status: BountyStatus = Field(default=BountyStatus.CREATED)
created_at: datetime = Field(default_factory=datetime.utcnow)
processed_at: Optional[datetime] = Field(default=None)
# Processing information
processing_attempts: int = Field(default=0)
error_message: Optional[str] = Field(default=None)
gas_used: Optional[int] = Field(default=None)
# Verification results
auto_verified: bool = Field(default=False)
verification_threshold_met: bool = Field(default=False)
performance_score: Optional[float] = Field(default=None)
# Metadata
integration_data: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_integration_hash_status", "columns": ["performance_hash", "status"]},
{"name": "ix_integration_bounty", "columns": ["bounty_id"]},
{"name": "ix_integration_created", "columns": ["created_at"]},
]}
)
class BountyStats(SQLModel, table=True):
"""Aggregated bounty statistics"""
__tablename__ = "bounty_stats"
stats_id: str = Field(primary_key=True, default_factory=lambda: f"stats_{uuid.uuid4().hex[:8]}")
# Time period
period_start: datetime = Field(index=True)
period_end: datetime = Field(index=True)
period_type: str = Field(default="daily") # daily, weekly, monthly
# Bounty counts
total_bounties: int = Field(default=0)
active_bounties: int = Field(default=0)
completed_bounties: int = Field(default=0)
expired_bounties: int = Field(default=0)
disputed_bounties: int = Field(default=0)
# Financial metrics
total_value_locked: float = Field(default=0.0)
total_rewards_paid: float = Field(default=0.0)
total_fees_collected: float = Field(default=0.0)
average_reward: float = Field(default=0.0)
# Performance metrics
success_rate: float = Field(default=0.0)
average_completion_time: Optional[float] = Field(default=None) # hours
average_accuracy: Optional[float] = Field(default=None)
# Participant metrics
unique_creators: int = Field(default=0)
unique_submitters: int = Field(default=0)
total_submissions: int = Field(default=0)
# Tier distribution
tier_distribution: Dict[str, int] = Field(default_factory=dict, sa_column=Column(JSON))
# Metadata
stats_metadata: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_stats_period", "columns": ["period_start", "period_end", "period_type"]},
{"name": "ix_stats_created", "columns": ["period_start"]},
]}
)
class EcosystemMetrics(SQLModel, table=True):
"""Ecosystem-wide metrics for dashboard"""
__tablename__ = "ecosystem_metrics"
metrics_id: str = Field(primary_key=True, default_factory=lambda: f"eco_{uuid.uuid4().hex[:8]}")
# Time period
timestamp: datetime = Field(default_factory=datetime.utcnow, index=True)
period_type: str = Field(default="hourly") # hourly, daily, weekly
# Developer metrics
active_developers: int = Field(default=0)
new_developers: int = Field(default=0)
developer_earnings_total: float = Field(default=0.0)
developer_earnings_average: float = Field(default=0.0)
# Agent metrics
total_agents: int = Field(default=0)
active_agents: int = Field(default=0)
agent_utilization_rate: float = Field(default=0.0)
average_agent_performance: float = Field(default=0.0)
# Staking metrics
total_staked: float = Field(default=0.0)
total_stakers: int = Field(default=0)
average_apy: float = Field(default=0.0)
staking_rewards_total: float = Field(default=0.0)
# Bounty metrics
active_bounties: int = Field(default=0)
bounty_completion_rate: float = Field(default=0.0)
average_bounty_reward: float = Field(default=0.0)
bounty_volume_total: float = Field(default=0.0)
# Treasury metrics
treasury_balance: float = Field(default=0.0)
treasury_inflow: float = Field(default=0.0)
treasury_outflow: float = Field(default=0.0)
dao_revenue: float = Field(default=0.0)
# Token metrics
token_circulating_supply: float = Field(default=0.0)
token_staked_percentage: float = Field(default=0.0)
token_burn_rate: float = Field(default=0.0)
# Metadata
metrics_data: Dict[str, Any] = Field(default_factory=dict, sa_column=Column(JSON))
# Indexes
__table_args__ = (
{"indexes": [
{"name": "ix_ecosystem_timestamp", "columns": ["timestamp", "period_type"]},
{"name": "ix_ecosystem_developers", "columns": ["active_developers"]},
{"name": "ix_ecosystem_staked", "columns": ["total_staked"]},
]}
)
# Update relationships
AgentStake.agent_metrics = Relationship(back_populates="stakes")

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))

View File

@@ -0,0 +1,618 @@
"""
Bounty Management Service
Business logic for AI agent bounty system with ZK-proof verification
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import select, func, and_, or_
from datetime import datetime, timedelta
import uuid
from ..domain.bounty import (
Bounty, BountySubmission, BountyStatus, BountyTier,
SubmissionStatus, BountyStats, BountyIntegration
)
from ..storage import get_session
from ..logging import get_logger
logger = get_logger(__name__)
class BountyService:
"""Service for managing AI agent bounties"""
def __init__(self, session: Session):
self.session = session
async def create_bounty(
self,
creator_id: str,
title: str,
description: str,
reward_amount: float,
tier: BountyTier,
performance_criteria: Dict[str, Any],
min_accuracy: float,
max_response_time: Optional[int],
deadline: datetime,
max_submissions: int,
requires_zk_proof: bool,
auto_verify_threshold: float,
tags: List[str],
category: Optional[str],
difficulty: Optional[str]
) -> Bounty:
"""Create a new bounty"""
try:
# Calculate fees
creation_fee = reward_amount * 0.005 # 0.5%
success_fee = reward_amount * 0.02 # 2%
platform_fee = reward_amount * 0.01 # 1%
bounty = Bounty(
title=title,
description=description,
reward_amount=reward_amount,
creator_id=creator_id,
tier=tier,
performance_criteria=performance_criteria,
min_accuracy=min_accuracy,
max_response_time=max_response_time,
deadline=deadline,
max_submissions=max_submissions,
requires_zk_proof=requires_zk_proof,
auto_verify_threshold=auto_verify_threshold,
tags=tags,
category=category,
difficulty=difficulty,
creation_fee=creation_fee,
success_fee=success_fee,
platform_fee=platform_fee
)
self.session.add(bounty)
self.session.commit()
self.session.refresh(bounty)
logger.info(f"Created bounty {bounty.bounty_id}: {title}")
return bounty
except Exception as e:
logger.error(f"Failed to create bounty: {e}")
self.session.rollback()
raise
async def get_bounty(self, bounty_id: str) -> Optional[Bounty]:
"""Get bounty by ID"""
try:
stmt = select(Bounty).where(Bounty.bounty_id == bounty_id)
result = self.session.execute(stmt).scalar_one_or_none()
return result
except Exception as e:
logger.error(f"Failed to get bounty {bounty_id}: {e}")
raise
async def get_bounties(
self,
status: Optional[BountyStatus] = None,
tier: Optional[BountyTier] = None,
creator_id: Optional[str] = None,
category: Optional[str] = None,
min_reward: Optional[float] = None,
max_reward: Optional[float] = None,
deadline_before: Optional[datetime] = None,
deadline_after: Optional[datetime] = None,
tags: Optional[List[str]] = None,
requires_zk_proof: Optional[bool] = None,
page: int = 1,
limit: int = 20
) -> List[Bounty]:
"""Get filtered list of bounties"""
try:
query = select(Bounty)
# Apply filters
if status:
query = query.where(Bounty.status == status)
if tier:
query = query.where(Bounty.tier == tier)
if creator_id:
query = query.where(Bounty.creator_id == creator_id)
if category:
query = query.where(Bounty.category == category)
if min_reward:
query = query.where(Bounty.reward_amount >= min_reward)
if max_reward:
query = query.where(Bounty.reward_amount <= max_reward)
if deadline_before:
query = query.where(Bounty.deadline <= deadline_before)
if deadline_after:
query = query.where(Bounty.deadline >= deadline_after)
if requires_zk_proof is not None:
query = query.where(Bounty.requires_zk_proof == requires_zk_proof)
# Apply tag filtering
if tags:
for tag in tags:
query = query.where(Bounty.tags.contains([tag]))
# Order by creation time (newest first)
query = query.order_by(Bounty.creation_time.desc())
# Apply pagination
offset = (page - 1) * limit
query = query.offset(offset).limit(limit)
result = self.session.execute(query).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to get bounties: {e}")
raise
async def create_submission(
self,
bounty_id: str,
submitter_address: str,
zk_proof: Optional[Dict[str, Any]],
performance_hash: str,
accuracy: float,
response_time: Optional[int],
compute_power: Optional[float],
energy_efficiency: Optional[float],
submission_data: Dict[str, Any],
test_results: Dict[str, Any]
) -> BountySubmission:
"""Create a bounty submission"""
try:
# Check if bounty exists and is active
bounty = await self.get_bounty(bounty_id)
if not bounty:
raise ValueError("Bounty not found")
if bounty.status != BountyStatus.ACTIVE:
raise ValueError("Bounty is not active")
if datetime.utcnow() > bounty.deadline:
raise ValueError("Bounty deadline has passed")
if bounty.submission_count >= bounty.max_submissions:
raise ValueError("Maximum submissions reached")
# Check if user has already submitted
existing_stmt = select(BountySubmission).where(
and_(
BountySubmission.bounty_id == bounty_id,
BountySubmission.submitter_address == submitter_address
)
)
existing = self.session.execute(existing_stmt).scalar_one_or_none()
if existing:
raise ValueError("Already submitted to this bounty")
submission = BountySubmission(
bounty_id=bounty_id,
submitter_address=submitter_address,
accuracy=accuracy,
response_time=response_time,
compute_power=compute_power,
energy_efficiency=energy_efficiency,
zk_proof=zk_proof or {},
performance_hash=performance_hash,
submission_data=submission_data,
test_results=test_results
)
self.session.add(submission)
# Update bounty submission count
bounty.submission_count += 1
self.session.commit()
self.session.refresh(submission)
logger.info(f"Created submission {submission.submission_id} for bounty {bounty_id}")
return submission
except Exception as e:
logger.error(f"Failed to create submission: {e}")
self.session.rollback()
raise
async def get_bounty_submissions(self, bounty_id: str) -> List[BountySubmission]:
"""Get all submissions for a bounty"""
try:
stmt = select(BountySubmission).where(
BountySubmission.bounty_id == bounty_id
).order_by(BountySubmission.submission_time.desc())
result = self.session.execute(stmt).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to get bounty submissions: {e}")
raise
async def verify_submission(
self,
bounty_id: str,
submission_id: str,
verified: bool,
verifier_address: str,
verification_notes: Optional[str] = None
) -> BountySubmission:
"""Verify a bounty submission"""
try:
stmt = select(BountySubmission).where(
and_(
BountySubmission.submission_id == submission_id,
BountySubmission.bounty_id == bounty_id
)
)
submission = self.session.execute(stmt).scalar_one_or_none()
if not submission:
raise ValueError("Submission not found")
if submission.status != SubmissionStatus.PENDING:
raise ValueError("Submission already processed")
# Update submission
submission.status = SubmissionStatus.VERIFIED if verified else SubmissionStatus.REJECTED
submission.verification_time = datetime.utcnow()
submission.verifier_address = verifier_address
# If verified, check if it meets bounty requirements
if verified:
bounty = await self.get_bounty(bounty_id)
if submission.accuracy >= bounty.min_accuracy:
# Complete the bounty
bounty.status = BountyStatus.COMPLETED
bounty.winning_submission_id = submission.submission_id
bounty.winner_address = submission.submitter_address
logger.info(f"Bounty {bounty_id} completed by {submission.submitter_address}")
self.session.commit()
self.session.refresh(submission)
return submission
except Exception as e:
logger.error(f"Failed to verify submission: {e}")
self.session.rollback()
raise
async def create_dispute(
self,
bounty_id: str,
submission_id: str,
disputer_address: str,
dispute_reason: str
) -> BountySubmission:
"""Create a dispute for a submission"""
try:
stmt = select(BountySubmission).where(
and_(
BountySubmission.submission_id == submission_id,
BountySubmission.bounty_id == bounty_id
)
)
submission = self.session.execute(stmt).scalar_one_or_none()
if not submission:
raise ValueError("Submission not found")
if submission.status != SubmissionStatus.VERIFIED:
raise ValueError("Can only dispute verified submissions")
if datetime.utcnow() - submission.verification_time > timedelta(days=1):
raise ValueError("Dispute window expired")
# Update submission
submission.status = SubmissionStatus.DISPUTED
submission.dispute_reason = dispute_reason
submission.dispute_time = datetime.utcnow()
# Update bounty status
bounty = await self.get_bounty(bounty_id)
bounty.status = BountyStatus.DISPUTED
self.session.commit()
self.session.refresh(submission)
logger.info(f"Created dispute for submission {submission_id}")
return submission
except Exception as e:
logger.error(f"Failed to create dispute: {e}")
self.session.rollback()
raise
async def get_user_created_bounties(
self,
user_address: str,
status: Optional[BountyStatus] = None,
page: int = 1,
limit: int = 20
) -> List[Bounty]:
"""Get bounties created by a user"""
try:
query = select(Bounty).where(Bounty.creator_id == user_address)
if status:
query = query.where(Bounty.status == status)
query = query.order_by(Bounty.creation_time.desc())
offset = (page - 1) * limit
query = query.offset(offset).limit(limit)
result = self.session.execute(query).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to get user created bounties: {e}")
raise
async def get_user_submissions(
self,
user_address: str,
status: Optional[SubmissionStatus] = None,
page: int = 1,
limit: int = 20
) -> List[BountySubmission]:
"""Get submissions made by a user"""
try:
query = select(BountySubmission).where(
BountySubmission.submitter_address == user_address
)
if status:
query = query.where(BountySubmission.status == status)
query = query.order_by(BountySubmission.submission_time.desc())
offset = (page - 1) * limit
query = query.offset(offset).limit(limit)
result = self.session.execute(query).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to get user submissions: {e}")
raise
async def get_leaderboard(
self,
period: str = "weekly",
limit: int = 50
) -> List[Dict[str, Any]]:
"""Get bounty leaderboard"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(weeks=1)
# Get top performers
stmt = select(
BountySubmission.submitter_address,
func.count(BountySubmission.submission_id).label('submissions'),
func.avg(BountySubmission.accuracy).label('avg_accuracy'),
func.sum(Bounty.reward_amount).label('total_rewards')
).join(Bounty).where(
and_(
BountySubmission.status == SubmissionStatus.VERIFIED,
BountySubmission.submission_time >= start_date
)
).group_by(BountySubmission.submitter_address).order_by(
func.sum(Bounty.reward_amount).desc()
).limit(limit)
result = self.session.execute(stmt).all()
leaderboard = []
for row in result:
leaderboard.append({
"address": row.submitter_address,
"submissions": row.submissions,
"avg_accuracy": float(row.avg_accuracy),
"total_rewards": float(row.total_rewards),
"rank": len(leaderboard) + 1
})
return leaderboard
except Exception as e:
logger.error(f"Failed to get leaderboard: {e}")
raise
async def get_bounty_stats(self, period: str = "monthly") -> BountyStats:
"""Get bounty statistics"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get statistics
total_stmt = select(func.count(Bounty.bounty_id)).where(
Bounty.creation_time >= start_date
)
total_bounties = self.session.execute(total_stmt).scalar() or 0
active_stmt = select(func.count(Bounty.bounty_id)).where(
and_(
Bounty.creation_time >= start_date,
Bounty.status == BountyStatus.ACTIVE
)
)
active_bounties = self.session.execute(active_stmt).scalar() or 0
completed_stmt = select(func.count(Bounty.bounty_id)).where(
and_(
Bounty.creation_time >= start_date,
Bounty.status == BountyStatus.COMPLETED
)
)
completed_bounties = self.session.execute(completed_stmt).scalar() or 0
# Financial metrics
total_locked_stmt = select(func.sum(Bounty.reward_amount)).where(
Bounty.creation_time >= start_date
)
total_value_locked = self.session.execute(total_locked_stmt).scalar() or 0.0
total_rewards_stmt = select(func.sum(Bounty.reward_amount)).where(
and_(
Bounty.creation_time >= start_date,
Bounty.status == BountyStatus.COMPLETED
)
)
total_rewards_paid = self.session.execute(total_rewards_stmt).scalar() or 0.0
# Success rate
success_rate = (completed_bounties / total_bounties * 100) if total_bounties > 0 else 0.0
# Average reward
avg_reward = total_value_locked / total_bounties if total_bounties > 0 else 0.0
# Tier distribution
tier_stmt = select(
Bounty.tier,
func.count(Bounty.bounty_id).label('count')
).where(
Bounty.creation_time >= start_date
).group_by(Bounty.tier)
tier_result = self.session.execute(tier_stmt).all()
tier_distribution = {row.tier.value: row.count for row in tier_result}
stats = BountyStats(
period_start=start_date,
period_end=datetime.utcnow(),
period_type=period,
total_bounties=total_bounties,
active_bounties=active_bounties,
completed_bounties=completed_bounties,
expired_bounties=0, # TODO: Implement expired counting
disputed_bounties=0, # TODO: Implement disputed counting
total_value_locked=total_value_locked,
total_rewards_paid=total_rewards_paid,
total_fees_collected=0, # TODO: Calculate fees
average_reward=avg_reward,
success_rate=success_rate,
tier_distribution=tier_distribution
)
return stats
except Exception as e:
logger.error(f"Failed to get bounty stats: {e}")
raise
async def get_categories(self) -> List[str]:
"""Get all bounty categories"""
try:
stmt = select(Bounty.category).where(
and_(
Bounty.category.isnot(None),
Bounty.category != ""
)
).distinct()
result = self.session.execute(stmt).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to get categories: {e}")
raise
async def get_popular_tags(self, limit: int = 100) -> List[str]:
"""Get popular bounty tags"""
try:
# This is a simplified implementation
# In production, you'd want to count tag usage
stmt = select(Bounty.tags).where(
func.array_length(Bounty.tags, 1) > 0
).limit(limit)
result = self.session.execute(stmt).scalars().all()
# Flatten and deduplicate tags
all_tags = []
for tags in result:
all_tags.extend(tags)
# Return unique tags (simplified - would need proper counting in production)
return list(set(all_tags))[:limit]
except Exception as e:
logger.error(f"Failed to get popular tags: {e}")
raise
async def search_bounties(
self,
query: str,
page: int = 1,
limit: int = 20
) -> List[Bounty]:
"""Search bounties by text"""
try:
# Simple text search implementation
search_pattern = f"%{query}%"
stmt = select(Bounty).where(
or_(
Bounty.title.ilike(search_pattern),
Bounty.description.ilike(search_pattern)
)
).order_by(Bounty.creation_time.desc())
offset = (page - 1) * limit
stmt = stmt.offset(offset).limit(limit)
result = self.session.execute(stmt).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to search bounties: {e}")
raise
async def expire_bounty(self, bounty_id: str) -> Bounty:
"""Expire a bounty"""
try:
bounty = await self.get_bounty(bounty_id)
if not bounty:
raise ValueError("Bounty not found")
if bounty.status != BountyStatus.ACTIVE:
raise ValueError("Bounty is not active")
if datetime.utcnow() <= bounty.deadline:
raise ValueError("Deadline has not passed")
bounty.status = BountyStatus.EXPIRED
self.session.commit()
self.session.refresh(bounty)
logger.info(f"Expired bounty {bounty_id}")
return bounty
except Exception as e:
logger.error(f"Failed to expire bounty: {e}")
self.session.rollback()
raise

View File

@@ -0,0 +1,840 @@
"""
Ecosystem Analytics Service
Business logic for developer ecosystem metrics and analytics
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import select, func, and_, or_
from datetime import datetime, timedelta
import uuid
from ..domain.bounty import (
EcosystemMetrics, BountyStats, AgentMetrics, AgentStake,
Bounty, BountySubmission, BountyStatus, PerformanceTier
)
from ..storage import get_session
from ..logging import get_logger
logger = get_logger(__name__)
class EcosystemService:
"""Service for ecosystem analytics and metrics"""
def __init__(self, session: Session):
self.session = session
async def get_developer_earnings(self, period: str = "monthly") -> Dict[str, Any]:
"""Get developer earnings metrics"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get total earnings from completed bounties
earnings_stmt = select(
func.sum(Bounty.reward_amount).label('total_earnings'),
func.count(func.distinct(Bounty.winner_address)).label('unique_earners'),
func.avg(Bounty.reward_amount).label('average_earnings')
).where(
and_(
Bounty.status == BountyStatus.COMPLETED,
Bounty.creation_time >= start_date
)
)
earnings_result = self.session.execute(earnings_stmt).first()
total_earnings = earnings_result.total_earnings or 0.0
unique_earners = earnings_result.unique_earners or 0
average_earnings = earnings_result.average_earnings or 0.0
# Get top earners
top_earners_stmt = select(
Bounty.winner_address,
func.sum(Bounty.reward_amount).label('total_earned'),
func.count(Bounty.bounty_id).label('bounties_won')
).where(
and_(
Bounty.status == BountyStatus.COMPLETED,
Bounty.creation_time >= start_date,
Bounty.winner_address.isnot(None)
)
).group_by(Bounty.winner_address).order_by(
func.sum(Bounty.reward_amount).desc()
).limit(10)
top_earners_result = self.session.execute(top_earners_stmt).all()
top_earners = [
{
"address": row.winner_address,
"total_earned": float(row.total_earned),
"bounties_won": row.bounties_won,
"rank": i + 1
}
for i, row in enumerate(top_earners_result)
]
# Calculate earnings growth (compare with previous period)
previous_start = start_date - timedelta(days=30) if period == "monthly" else start_date - timedelta(days=7)
previous_earnings_stmt = select(func.sum(Bounty.reward_amount)).where(
and_(
Bounty.status == BountyStatus.COMPLETED,
Bounty.creation_time >= previous_start,
Bounty.creation_time < start_date
)
)
previous_earnings = self.session.execute(previous_earnings_stmt).scalar() or 0.0
earnings_growth = ((total_earnings - previous_earnings) / previous_earnings * 100) if previous_earnings > 0 else 0.0
return {
"total_earnings": total_earnings,
"average_earnings": average_earnings,
"top_earners": top_earners,
"earnings_growth": earnings_growth,
"active_developers": unique_earners
}
except Exception as e:
logger.error(f"Failed to get developer earnings: {e}")
raise
async def get_agent_utilization(self, period: str = "monthly") -> Dict[str, Any]:
"""Get agent utilization metrics"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get agent metrics
agents_stmt = select(
func.count(AgentMetrics.agent_wallet).label('total_agents'),
func.sum(AgentMetrics.total_submissions).label('total_submissions'),
func.avg(AgentMetrics.average_accuracy).label('avg_accuracy')
).where(
AgentMetrics.last_update_time >= start_date
)
agents_result = self.session.execute(agents_stmt).first()
total_agents = agents_result.total_agents or 0
total_submissions = agents_result.total_submissions or 0
average_accuracy = agents_result.avg_accuracy or 0.0
# Get active agents (with submissions in period)
active_agents_stmt = select(func.count(func.distinct(BountySubmission.submitter_address))).where(
BountySubmission.submission_time >= start_date
)
active_agents = self.session.execute(active_agents_stmt).scalar() or 0
# Calculate utilization rate
utilization_rate = (active_agents / total_agents * 100) if total_agents > 0 else 0.0
# Get top utilized agents
top_agents_stmt = select(
BountySubmission.submitter_address,
func.count(BountySubmission.submission_id).label('submissions'),
func.avg(BountySubmission.accuracy).label('avg_accuracy')
).where(
BountySubmission.submission_time >= start_date
).group_by(BountySubmission.submitter_address).order_by(
func.count(BountySubmission.submission_id).desc()
).limit(10)
top_agents_result = self.session.execute(top_agents_stmt).all()
top_utilized_agents = [
{
"agent_wallet": row.submitter_address,
"submissions": row.submissions,
"avg_accuracy": float(row.avg_accuracy),
"rank": i + 1
}
for i, row in enumerate(top_agents_result)
]
# Get performance distribution
performance_stmt = select(
AgentMetrics.current_tier,
func.count(AgentMetrics.agent_wallet).label('count')
).where(
AgentMetrics.last_update_time >= start_date
).group_by(AgentMetrics.current_tier)
performance_result = self.session.execute(performance_stmt).all()
performance_distribution = {row.current_tier.value: row.count for row in performance_result}
return {
"total_agents": total_agents,
"active_agents": active_agents,
"utilization_rate": utilization_rate,
"top_utilized_agents": top_utilized_agents,
"average_performance": average_accuracy,
"performance_distribution": performance_distribution
}
except Exception as e:
logger.error(f"Failed to get agent utilization: {e}")
raise
async def get_treasury_allocation(self, period: str = "monthly") -> Dict[str, Any]:
"""Get DAO treasury allocation metrics"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get bounty fees (treasury inflow)
inflow_stmt = select(
func.sum(Bounty.creation_fee + Bounty.success_fee + Bounty.platform_fee).label('total_inflow')
).where(
Bounty.creation_time >= start_date
)
total_inflow = self.session.execute(inflow_stmt).scalar() or 0.0
# Get rewards paid (treasury outflow)
outflow_stmt = select(
func.sum(Bounty.reward_amount).label('total_outflow')
).where(
and_(
Bounty.status == BountyStatus.COMPLETED,
Bounty.creation_time >= start_date
)
)
total_outflow = self.session.execute(outflow_stmt).scalar() or 0.0
# Calculate DAO revenue (fees - rewards)
dao_revenue = total_inflow - total_outflow
# Get allocation breakdown by category
allocation_breakdown = {
"bounty_fees": total_inflow,
"rewards_paid": total_outflow,
"platform_revenue": dao_revenue
}
# Calculate burn rate
burn_rate = (total_outflow / total_inflow * 100) if total_inflow > 0 else 0.0
# Mock treasury balance (would come from actual treasury tracking)
treasury_balance = 1000000.0 # Mock value
return {
"treasury_balance": treasury_balance,
"total_inflow": total_inflow,
"total_outflow": total_outflow,
"dao_revenue": dao_revenue,
"allocation_breakdown": allocation_breakdown,
"burn_rate": burn_rate
}
except Exception as e:
logger.error(f"Failed to get treasury allocation: {e}")
raise
async def get_staking_metrics(self, period: str = "monthly") -> Dict[str, Any]:
"""Get staking system metrics"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get staking metrics
staking_stmt = select(
func.sum(AgentStake.amount).label('total_staked'),
func.count(func.distinct(AgentStake.staker_address)).label('total_stakers'),
func.avg(AgentStake.current_apy).label('avg_apy')
).where(
AgentStake.start_time >= start_date
)
staking_result = self.session.execute(staking_stmt).first()
total_staked = staking_result.total_staked or 0.0
total_stakers = staking_result.total_stakers or 0
average_apy = staking_result.avg_apy or 0.0
# Get total rewards distributed
rewards_stmt = select(
func.sum(AgentMetrics.total_rewards_distributed).label('total_rewards')
).where(
AgentMetrics.last_update_time >= start_date
)
total_rewards = self.session.execute(rewards_stmt).scalar() or 0.0
# Get top staking pools
top_pools_stmt = select(
AgentStake.agent_wallet,
func.sum(AgentStake.amount).label('total_staked'),
func.count(AgentStake.stake_id).label('stake_count'),
func.avg(AgentStake.current_apy).label('avg_apy')
).where(
AgentStake.start_time >= start_date
).group_by(AgentStake.agent_wallet).order_by(
func.sum(AgentStake.amount).desc()
).limit(10)
top_pools_result = self.session.execute(top_pools_stmt).all()
top_staking_pools = [
{
"agent_wallet": row.agent_wallet,
"total_staked": float(row.total_staked),
"stake_count": row.stake_count,
"avg_apy": float(row.avg_apy),
"rank": i + 1
}
for i, row in enumerate(top_pools_result)
]
# Get tier distribution
tier_stmt = select(
AgentStake.agent_tier,
func.count(AgentStake.stake_id).label('count')
).where(
AgentStake.start_time >= start_date
).group_by(AgentStake.agent_tier)
tier_result = self.session.execute(tier_stmt).all()
tier_distribution = {row.agent_tier.value: row.count for row in tier_result}
return {
"total_staked": total_staked,
"total_stakers": total_stakers,
"average_apy": average_apy,
"staking_rewards_total": total_rewards,
"top_staking_pools": top_staking_pools,
"tier_distribution": tier_distribution
}
except Exception as e:
logger.error(f"Failed to get staking metrics: {e}")
raise
async def get_bounty_analytics(self, period: str = "monthly") -> Dict[str, Any]:
"""Get bounty system analytics"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get bounty counts
bounty_stmt = select(
func.count(Bounty.bounty_id).label('total_bounties'),
func.count(func.distinct(Bounty.bounty_id)).filter(
Bounty.status == BountyStatus.ACTIVE
).label('active_bounties')
).where(
Bounty.creation_time >= start_date
)
bounty_result = self.session.execute(bounty_stmt).first()
total_bounties = bounty_result.total_bounties or 0
active_bounties = bounty_result.active_bounties or 0
# Get completion rate
completed_stmt = select(func.count(Bounty.bounty_id)).where(
and_(
Bounty.creation_time >= start_date,
Bounty.status == BountyStatus.COMPLETED
)
)
completed_bounties = self.session.execute(completed_stmt).scalar() or 0
completion_rate = (completed_bounties / total_bounties * 100) if total_bounties > 0 else 0.0
# Get average reward and volume
reward_stmt = select(
func.avg(Bounty.reward_amount).label('avg_reward'),
func.sum(Bounty.reward_amount).label('total_volume')
).where(
Bounty.creation_time >= start_date
)
reward_result = self.session.execute(reward_stmt).first()
average_reward = reward_result.avg_reward or 0.0
total_volume = reward_result.total_volume or 0.0
# Get category distribution
category_stmt = select(
Bounty.category,
func.count(Bounty.bounty_id).label('count')
).where(
and_(
Bounty.creation_time >= start_date,
Bounty.category.isnot(None),
Bounty.category != ""
)
).group_by(Bounty.category)
category_result = self.session.execute(category_stmt).all()
category_distribution = {row.category: row.count for row in category_result}
# Get difficulty distribution
difficulty_stmt = select(
Bounty.difficulty,
func.count(Bounty.bounty_id).label('count')
).where(
and_(
Bounty.creation_time >= start_date,
Bounty.difficulty.isnot(None),
Bounty.difficulty != ""
)
).group_by(Bounty.difficulty)
difficulty_result = self.session.execute(difficulty_stmt).all()
difficulty_distribution = {row.difficulty: row.count for row in difficulty_result}
return {
"active_bounties": active_bounties,
"completion_rate": completion_rate,
"average_reward": average_reward,
"total_volume": total_volume,
"category_distribution": category_distribution,
"difficulty_distribution": difficulty_distribution
}
except Exception as e:
logger.error(f"Failed to get bounty analytics: {e}")
raise
async def get_ecosystem_overview(self, period_type: str = "daily") -> Dict[str, Any]:
"""Get comprehensive ecosystem overview"""
try:
# Get all metrics
developer_earnings = await self.get_developer_earnings(period_type)
agent_utilization = await self.get_agent_utilization(period_type)
treasury_allocation = await self.get_treasury_allocation(period_type)
staking_metrics = await self.get_staking_metrics(period_type)
bounty_analytics = await self.get_bounty_analytics(period_type)
# Calculate health score
health_score = await self._calculate_health_score({
"developer_earnings": developer_earnings,
"agent_utilization": agent_utilization,
"treasury_allocation": treasury_allocation,
"staking_metrics": staking_metrics,
"bounty_analytics": bounty_analytics
})
# Calculate growth indicators
growth_indicators = await self._calculate_growth_indicators(period_type)
return {
"developer_earnings": developer_earnings,
"agent_utilization": agent_utilization,
"treasury_allocation": treasury_allocation,
"staking_metrics": staking_metrics,
"bounty_analytics": bounty_analytics,
"health_score": health_score,
"growth_indicators": growth_indicators
}
except Exception as e:
logger.error(f"Failed to get ecosystem overview: {e}")
raise
async def get_time_series_metrics(
self,
period_type: str = "daily",
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
limit: int = 100
) -> List[Dict[str, Any]]:
"""Get time-series ecosystem metrics"""
try:
if not start_date:
start_date = datetime.utcnow() - timedelta(days=30)
if not end_date:
end_date = datetime.utcnow()
# This is a simplified implementation
# In production, you'd want more sophisticated time-series aggregation
metrics = []
current_date = start_date
while current_date <= end_date and len(metrics) < limit:
# Create a sample metric for each period
metric = EcosystemMetrics(
timestamp=current_date,
period_type=period_type,
active_developers=100 + len(metrics) * 2, # Mock data
new_developers=5 + len(metrics), # Mock data
developer_earnings_total=1000.0 * (len(metrics) + 1), # Mock data
total_agents=50 + len(metrics), # Mock data
active_agents=40 + len(metrics), # Mock data
total_staked=10000.0 * (len(metrics) + 1), # Mock data
total_stakers=20 + len(metrics), # Mock data
active_bounties=10 + len(metrics), # Mock data
bounty_completion_rate=80.0 + len(metrics), # Mock data
treasury_balance=1000000.0, # Mock data
dao_revenue=1000.0 * (len(metrics) + 1) # Mock data
)
metrics.append({
"timestamp": metric.timestamp,
"active_developers": metric.active_developers,
"developer_earnings_total": metric.developer_earnings_total,
"total_agents": metric.total_agents,
"total_staked": metric.total_staked,
"active_bounties": metric.active_bounties,
"dao_revenue": metric.dao_revenue
})
# Move to next period
if period_type == "hourly":
current_date += timedelta(hours=1)
elif period_type == "daily":
current_date += timedelta(days=1)
elif period_type == "weekly":
current_date += timedelta(weeks=1)
elif period_type == "monthly":
current_date += timedelta(days=30)
return metrics
except Exception as e:
logger.error(f"Failed to get time-series metrics: {e}")
raise
async def calculate_health_score(self, metrics_data: Dict[str, Any]) -> float:
"""Calculate overall ecosystem health score"""
try:
scores = []
# Developer earnings health (0-100)
earnings = metrics_data.get("developer_earnings", {})
earnings_score = min(100, earnings.get("earnings_growth", 0) + 50)
scores.append(earnings_score)
# Agent utilization health (0-100)
utilization = metrics_data.get("agent_utilization", {})
utilization_score = utilization.get("utilization_rate", 0)
scores.append(utilization_score)
# Staking health (0-100)
staking = metrics_data.get("staking_metrics", {})
staking_score = min(100, staking.get("total_staked", 0) / 100) # Scale down
scores.append(staking_score)
# Bounty health (0-100)
bounty = metrics_data.get("bounty_analytics", {})
bounty_score = bounty.get("completion_rate", 0)
scores.append(bounty_score)
# Treasury health (0-100)
treasury = metrics_data.get("treasury_allocation", {})
treasury_score = max(0, 100 - treasury.get("burn_rate", 0))
scores.append(treasury_score)
# Calculate weighted average
weights = [0.25, 0.2, 0.2, 0.2, 0.15] # Developer earnings weighted highest
health_score = sum(score * weight for score, weight in zip(scores, weights))
return round(health_score, 2)
except Exception as e:
logger.error(f"Failed to calculate health score: {e}")
return 50.0 # Default to neutral score
async def _calculate_growth_indicators(self, period: str) -> Dict[str, float]:
"""Calculate growth indicators"""
try:
# This is a simplified implementation
# In production, you'd compare with previous periods
return {
"developer_growth": 15.5, # Mock data
"agent_growth": 12.3, # Mock data
"staking_growth": 25.8, # Mock data
"bounty_growth": 18.2, # Mock data
"revenue_growth": 22.1 # Mock data
}
except Exception as e:
logger.error(f"Failed to calculate growth indicators: {e}")
return {}
async def get_top_performers(
self,
category: str = "all",
period: str = "monthly",
limit: int = 50
) -> List[Dict[str, Any]]:
"""Get top performers in different categories"""
try:
performers = []
if category in ["all", "developers"]:
# Get top developers
developer_earnings = await self.get_developer_earnings(period)
performers.extend([
{
"type": "developer",
"address": performer["address"],
"metric": "total_earned",
"value": performer["total_earned"],
"rank": performer["rank"]
}
for performer in developer_earnings.get("top_earners", [])
])
if category in ["all", "agents"]:
# Get top agents
agent_utilization = await self.get_agent_utilization(period)
performers.extend([
{
"type": "agent",
"address": performer["agent_wallet"],
"metric": "submissions",
"value": performer["submissions"],
"rank": performer["rank"]
}
for performer in agent_utilization.get("top_utilized_agents", [])
])
# Sort by value and limit
performers.sort(key=lambda x: x["value"], reverse=True)
return performers[:limit]
except Exception as e:
logger.error(f"Failed to get top performers: {e}")
raise
async def get_predictions(
self,
metric: str = "all",
horizon: int = 30
) -> Dict[str, Any]:
"""Get ecosystem predictions based on historical data"""
try:
# This is a simplified implementation
# In production, you'd use actual ML models
predictions = {
"earnings_prediction": 15000.0 * (1 + horizon / 30), # Mock linear growth
"staking_prediction": 50000.0 * (1 + horizon / 30), # Mock linear growth
"bounty_prediction": 100 * (1 + horizon / 30), # Mock linear growth
"confidence": 0.75, # Mock confidence score
"model": "linear_regression" # Mock model name
}
if metric != "all":
return {f"{metric}_prediction": predictions.get(f"{metric}_prediction", 0)}
return predictions
except Exception as e:
logger.error(f"Failed to get predictions: {e}")
raise
async def get_alerts(self, severity: str = "all") -> List[Dict[str, Any]]:
"""Get ecosystem alerts and anomalies"""
try:
# This is a simplified implementation
# In production, you'd have actual alerting logic
alerts = [
{
"id": "alert_1",
"type": "performance",
"severity": "medium",
"message": "Agent utilization dropped below 70%",
"timestamp": datetime.utcnow() - timedelta(hours=2),
"resolved": False
},
{
"id": "alert_2",
"type": "financial",
"severity": "low",
"message": "Bounty completion rate decreased by 5%",
"timestamp": datetime.utcnow() - timedelta(hours=6),
"resolved": False
}
]
if severity != "all":
alerts = [alert for alert in alerts if alert["severity"] == severity]
return alerts
except Exception as e:
logger.error(f"Failed to get alerts: {e}")
raise
async def get_period_comparison(
self,
current_period: str = "monthly",
compare_period: str = "previous",
custom_start_date: Optional[datetime] = None,
custom_end_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""Compare ecosystem metrics between periods"""
try:
# Get current period metrics
current_metrics = await self.get_ecosystem_overview(current_period)
# Get comparison period metrics
if compare_period == "previous":
comparison_metrics = await self.get_ecosystem_overview(current_period)
else:
# For custom comparison, you'd implement specific logic
comparison_metrics = await self.get_ecosystem_overview(current_period)
# Calculate differences
comparison = {
"developer_earnings": {
"current": current_metrics["developer_earnings"]["total_earnings"],
"previous": comparison_metrics["developer_earnings"]["total_earnings"],
"change": current_metrics["developer_earnings"]["total_earnings"] - comparison_metrics["developer_earnings"]["total_earnings"],
"change_percent": ((current_metrics["developer_earnings"]["total_earnings"] - comparison_metrics["developer_earnings"]["total_earnings"]) / comparison_metrics["developer_earnings"]["total_earnings"] * 100) if comparison_metrics["developer_earnings"]["total_earnings"] > 0 else 0
},
"staking_metrics": {
"current": current_metrics["staking_metrics"]["total_staked"],
"previous": comparison_metrics["staking_metrics"]["total_staked"],
"change": current_metrics["staking_metrics"]["total_staked"] - comparison_metrics["staking_metrics"]["total_staked"],
"change_percent": ((current_metrics["staking_metrics"]["total_staked"] - comparison_metrics["staking_metrics"]["total_staked"]) / comparison_metrics["staking_metrics"]["total_staked"] * 100) if comparison_metrics["staking_metrics"]["total_staked"] > 0 else 0
}
}
return {
"current_period": current_period,
"compare_period": compare_period,
"comparison": comparison,
"summary": {
"overall_trend": "positive" if comparison["developer_earnings"]["change_percent"] > 0 else "negative",
"key_insights": [
"Developer earnings increased by {:.1f}%".format(comparison["developer_earnings"]["change_percent"]),
"Total staked changed by {:.1f}%".format(comparison["staking_metrics"]["change_percent"])
]
}
}
except Exception as e:
logger.error(f"Failed to get period comparison: {e}")
raise
async def export_data(
self,
format: str = "json",
period_type: str = "daily",
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""Export ecosystem data in various formats"""
try:
# Get the data
metrics = await self.get_time_series_metrics(period_type, start_date, end_date)
# Mock export URL generation
export_url = f"/exports/ecosystem_data_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.{format}"
return {
"url": export_url,
"file_size": len(str(metrics)) * 0.001, # Mock file size in KB
"expires_at": datetime.utcnow() + timedelta(hours=24),
"record_count": len(metrics)
}
except Exception as e:
logger.error(f"Failed to export data: {e}")
raise
async def get_real_time_metrics(self) -> Dict[str, Any]:
"""Get real-time ecosystem metrics"""
try:
# This would typically connect to real-time data sources
# For now, return current snapshot
return {
"active_developers": 150,
"active_agents": 75,
"total_staked": 125000.0,
"active_bounties": 25,
"current_apy": 7.5,
"recent_submissions": 12,
"recent_completions": 8,
"system_load": 45.2 # Mock system load percentage
}
except Exception as e:
logger.error(f"Failed to get real-time metrics: {e}")
raise
async def get_kpi_dashboard(self) -> Dict[str, Any]:
"""Get KPI dashboard with key performance indicators"""
try:
return {
"developer_kpis": {
"total_developers": 1250,
"active_developers": 150,
"average_earnings": 2500.0,
"retention_rate": 85.5
},
"agent_kpis": {
"total_agents": 500,
"active_agents": 75,
"average_accuracy": 87.2,
"utilization_rate": 78.5
},
"staking_kpis": {
"total_staked": 125000.0,
"total_stakers": 350,
"average_apy": 7.5,
"tvl_growth": 15.2
},
"bounty_kpis": {
"active_bounties": 25,
"completion_rate": 82.5,
"average_reward": 1500.0,
"time_to_completion": 4.2 # days
},
"financial_kpis": {
"treasury_balance": 1000000.0,
"monthly_revenue": 25000.0,
"burn_rate": 12.5,
"profit_margin": 65.2
}
}
except Exception as e:
logger.error(f"Failed to get KPI dashboard: {e}")
raise

View File

@@ -0,0 +1,881 @@
"""
Staking Management Service
Business logic for AI agent staking system with reputation-based yield farming
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import select, func, and_, or_
from datetime import datetime, timedelta
import uuid
from ..domain.bounty import (
AgentStake, AgentMetrics, StakingPool, StakeStatus,
PerformanceTier, EcosystemMetrics
)
from ..storage import get_session
from ..logging import get_logger
logger = get_logger(__name__)
class StakingService:
"""Service for managing AI agent staking"""
def __init__(self, session: Session):
self.session = session
async def create_stake(
self,
staker_address: str,
agent_wallet: str,
amount: float,
lock_period: int,
auto_compound: bool
) -> AgentStake:
"""Create a new stake on an agent wallet"""
try:
# Validate agent is supported
agent_metrics = await self.get_agent_metrics(agent_wallet)
if not agent_metrics:
raise ValueError("Agent not supported for staking")
# Calculate APY
current_apy = await self.calculate_apy(agent_wallet, lock_period)
# Calculate end time
end_time = datetime.utcnow() + timedelta(days=lock_period)
stake = AgentStake(
staker_address=staker_address,
agent_wallet=agent_wallet,
amount=amount,
lock_period=lock_period,
end_time=end_time,
current_apy=current_apy,
agent_tier=agent_metrics.current_tier,
auto_compound=auto_compound
)
self.session.add(stake)
# Update agent metrics
agent_metrics.total_staked += amount
if agent_metrics.total_staked == amount:
agent_metrics.staker_count = 1
else:
agent_metrics.staker_count += 1
# Update staking pool
await self._update_staking_pool(agent_wallet, staker_address, amount, True)
self.session.commit()
self.session.refresh(stake)
logger.info(f"Created stake {stake.stake_id}: {amount} on {agent_wallet}")
return stake
except Exception as e:
logger.error(f"Failed to create stake: {e}")
self.session.rollback()
raise
async def get_stake(self, stake_id: str) -> Optional[AgentStake]:
"""Get stake by ID"""
try:
stmt = select(AgentStake).where(AgentStake.stake_id == stake_id)
result = self.session.execute(stmt).scalar_one_or_none()
return result
except Exception as e:
logger.error(f"Failed to get stake {stake_id}: {e}")
raise
async def get_user_stakes(
self,
user_address: str,
status: Optional[StakeStatus] = None,
agent_wallet: Optional[str] = None,
min_amount: Optional[float] = None,
max_amount: Optional[float] = None,
agent_tier: Optional[PerformanceTier] = None,
auto_compound: Optional[bool] = None,
page: int = 1,
limit: int = 20
) -> List[AgentStake]:
"""Get filtered list of user's stakes"""
try:
query = select(AgentStake).where(AgentStake.staker_address == user_address)
# Apply filters
if status:
query = query.where(AgentStake.status == status)
if agent_wallet:
query = query.where(AgentStake.agent_wallet == agent_wallet)
if min_amount:
query = query.where(AgentStake.amount >= min_amount)
if max_amount:
query = query.where(AgentStake.amount <= max_amount)
if agent_tier:
query = query.where(AgentStake.agent_tier == agent_tier)
if auto_compound is not None:
query = query.where(AgentStake.auto_compound == auto_compound)
# Order by creation time (newest first)
query = query.order_by(AgentStake.start_time.desc())
# Apply pagination
offset = (page - 1) * limit
query = query.offset(offset).limit(limit)
result = self.session.execute(query).scalars().all()
return list(result)
except Exception as e:
logger.error(f"Failed to get user stakes: {e}")
raise
async def add_to_stake(self, stake_id: str, additional_amount: float) -> AgentStake:
"""Add more tokens to an existing stake"""
try:
stake = await self.get_stake(stake_id)
if not stake:
raise ValueError("Stake not found")
if stake.status != StakeStatus.ACTIVE:
raise ValueError("Stake is not active")
# Update stake amount
stake.amount += additional_amount
# Recalculate APY
stake.current_apy = await self.calculate_apy(stake.agent_wallet, stake.lock_period)
# Update agent metrics
agent_metrics = await self.get_agent_metrics(stake.agent_wallet)
if agent_metrics:
agent_metrics.total_staked += additional_amount
# Update staking pool
await self._update_staking_pool(stake.agent_wallet, stake.staker_address, additional_amount, True)
self.session.commit()
self.session.refresh(stake)
logger.info(f"Added {additional_amount} to stake {stake_id}")
return stake
except Exception as e:
logger.error(f"Failed to add to stake: {e}")
self.session.rollback()
raise
async def unbond_stake(self, stake_id: str) -> AgentStake:
"""Initiate unbonding for a stake"""
try:
stake = await self.get_stake(stake_id)
if not stake:
raise ValueError("Stake not found")
if stake.status != StakeStatus.ACTIVE:
raise ValueError("Stake is not active")
if datetime.utcnow() < stake.end_time:
raise ValueError("Lock period has not ended")
# Calculate final rewards
await self._calculate_rewards(stake_id)
stake.status = StakeStatus.UNBONDING
stake.unbonding_time = datetime.utcnow()
self.session.commit()
self.session.refresh(stake)
logger.info(f"Initiated unbonding for stake {stake_id}")
return stake
except Exception as e:
logger.error(f"Failed to unbond stake: {e}")
self.session.rollback()
raise
async def complete_unbonding(self, stake_id: str) -> Dict[str, float]:
"""Complete unbonding and return stake + rewards"""
try:
stake = await self.get_stake(stake_id)
if not stake:
raise ValueError("Stake not found")
if stake.status != StakeStatus.UNBONDING:
raise ValueError("Stake is not unbonding")
# Calculate penalty if applicable
penalty = 0.0
total_amount = stake.amount
if stake.unbonding_time and datetime.utcnow() < stake.unbonding_time + timedelta(days=30):
penalty = total_amount * 0.10 # 10% early unbond penalty
total_amount -= penalty
# Update status
stake.status = StakeStatus.COMPLETED
# Update agent metrics
agent_metrics = await self.get_agent_metrics(stake.agent_wallet)
if agent_metrics:
agent_metrics.total_staked -= stake.amount
if agent_metrics.total_staked <= 0:
agent_metrics.staker_count = 0
else:
agent_metrics.staker_count -= 1
# Update staking pool
await self._update_staking_pool(stake.agent_wallet, stake.staker_address, stake.amount, False)
self.session.commit()
result = {
"total_amount": total_amount,
"total_rewards": stake.accumulated_rewards,
"penalty": penalty
}
logger.info(f"Completed unbonding for stake {stake_id}")
return result
except Exception as e:
logger.error(f"Failed to complete unbonding: {e}")
self.session.rollback()
raise
async def calculate_rewards(self, stake_id: str) -> float:
"""Calculate current rewards for a stake"""
try:
stake = await self.get_stake(stake_id)
if not stake:
raise ValueError("Stake not found")
if stake.status != StakeStatus.ACTIVE:
return stake.accumulated_rewards
# Calculate time-based rewards
time_elapsed = datetime.utcnow() - stake.last_reward_time
yearly_rewards = (stake.amount * stake.current_apy) / 100
current_rewards = (yearly_rewards * time_elapsed.total_seconds()) / (365 * 24 * 3600)
return stake.accumulated_rewards + current_rewards
except Exception as e:
logger.error(f"Failed to calculate rewards: {e}")
raise
async def get_agent_metrics(self, agent_wallet: str) -> Optional[AgentMetrics]:
"""Get agent performance metrics"""
try:
stmt = select(AgentMetrics).where(AgentMetrics.agent_wallet == agent_wallet)
result = self.session.execute(stmt).scalar_one_or_none()
return result
except Exception as e:
logger.error(f"Failed to get agent metrics: {e}")
raise
async def get_staking_pool(self, agent_wallet: str) -> Optional[StakingPool]:
"""Get staking pool for an agent"""
try:
stmt = select(StakingPool).where(StakingPool.agent_wallet == agent_wallet)
result = self.session.execute(stmt).scalar_one_or_none()
return result
except Exception as e:
logger.error(f"Failed to get staking pool: {e}")
raise
async def calculate_apy(self, agent_wallet: str, lock_period: int) -> float:
"""Calculate APY for staking on an agent"""
try:
# Base APY
base_apy = 5.0
# Get agent metrics
agent_metrics = await self.get_agent_metrics(agent_wallet)
if not agent_metrics:
return base_apy
# Tier multiplier
tier_multipliers = {
PerformanceTier.BRONZE: 1.0,
PerformanceTier.SILVER: 1.2,
PerformanceTier.GOLD: 1.5,
PerformanceTier.PLATINUM: 2.0,
PerformanceTier.DIAMOND: 3.0
}
tier_multiplier = tier_multipliers.get(agent_metrics.current_tier, 1.0)
# Lock period multiplier
lock_multipliers = {
30: 1.1, # 30 days
90: 1.25, # 90 days
180: 1.5, # 180 days
365: 2.0 # 365 days
}
lock_multiplier = lock_multipliers.get(lock_period, 1.0)
# Calculate final APY
apy = base_apy * tier_multiplier * lock_multiplier
# Cap at maximum
return min(apy, 20.0) # Max 20% APY
except Exception as e:
logger.error(f"Failed to calculate APY: {e}")
return 5.0 # Return base APY on error
async def update_agent_performance(
self,
agent_wallet: str,
accuracy: float,
successful: bool,
response_time: Optional[float] = None,
compute_power: Optional[float] = None,
energy_efficiency: Optional[float] = None
) -> AgentMetrics:
"""Update agent performance metrics"""
try:
# Get or create agent metrics
agent_metrics = await self.get_agent_metrics(agent_wallet)
if not agent_metrics:
agent_metrics = AgentMetrics(
agent_wallet=agent_wallet,
current_tier=PerformanceTier.BRONZE,
tier_score=60.0
)
self.session.add(agent_metrics)
# Update performance metrics
agent_metrics.total_submissions += 1
if successful:
agent_metrics.successful_submissions += 1
# Update average accuracy
total_accuracy = agent_metrics.average_accuracy * (agent_metrics.total_submissions - 1) + accuracy
agent_metrics.average_accuracy = total_accuracy / agent_metrics.total_submissions
# Update success rate
agent_metrics.success_rate = (agent_metrics.successful_submissions / agent_metrics.total_submissions) * 100
# Update other metrics
if response_time:
if agent_metrics.average_response_time is None:
agent_metrics.average_response_time = response_time
else:
agent_metrics.average_response_time = (agent_metrics.average_response_time + response_time) / 2
if energy_efficiency:
agent_metrics.energy_efficiency_score = energy_efficiency
# Calculate new tier
new_tier = await self._calculate_agent_tier(agent_metrics)
old_tier = agent_metrics.current_tier
if new_tier != old_tier:
agent_metrics.current_tier = new_tier
agent_metrics.tier_score = await self._get_tier_score(new_tier)
# Update APY for all active stakes on this agent
await self._update_stake_apy_for_agent(agent_wallet, new_tier)
agent_metrics.last_update_time = datetime.utcnow()
self.session.commit()
self.session.refresh(agent_metrics)
logger.info(f"Updated performance for agent {agent_wallet}")
return agent_metrics
except Exception as e:
logger.error(f"Failed to update agent performance: {e}")
self.session.rollback()
raise
async def distribute_earnings(
self,
agent_wallet: str,
total_earnings: float,
distribution_data: Dict[str, Any]
) -> Dict[str, Any]:
"""Distribute agent earnings to stakers"""
try:
# Get staking pool
pool = await self.get_staking_pool(agent_wallet)
if not pool or pool.total_staked == 0:
raise ValueError("No stakers in pool")
# Calculate platform fee (1%)
platform_fee = total_earnings * 0.01
distributable_amount = total_earnings - platform_fee
# Distribute to stakers proportionally
total_distributed = 0.0
staker_count = 0
# Get active stakes for this agent
stmt = select(AgentStake).where(
and_(
AgentStake.agent_wallet == agent_wallet,
AgentStake.status == StakeStatus.ACTIVE
)
)
stakes = self.session.execute(stmt).scalars().all()
for stake in stakes:
# Calculate staker's share
staker_share = (distributable_amount * stake.amount) / pool.total_staked
if staker_share > 0:
stake.accumulated_rewards += staker_share
total_distributed += staker_share
staker_count += 1
# Update pool metrics
pool.total_rewards += total_distributed
pool.last_distribution_time = datetime.utcnow()
# Update agent metrics
agent_metrics = await self.get_agent_metrics(agent_wallet)
if agent_metrics:
agent_metrics.total_rewards_distributed += total_distributed
self.session.commit()
result = {
"total_distributed": total_distributed,
"staker_count": staker_count,
"platform_fee": platform_fee
}
logger.info(f"Distributed {total_distributed} earnings to {staker_count} stakers")
return result
except Exception as e:
logger.error(f"Failed to distribute earnings: {e}")
self.session.rollback()
raise
async def get_supported_agents(
self,
page: int = 1,
limit: int = 50,
tier: Optional[PerformanceTier] = None
) -> List[Dict[str, Any]]:
"""Get list of supported agents for staking"""
try:
query = select(AgentMetrics)
if tier:
query = query.where(AgentMetrics.current_tier == tier)
query = query.order_by(AgentMetrics.total_staked.desc())
offset = (page - 1) * limit
query = query.offset(offset).limit(limit)
result = self.session.execute(query).scalars().all()
agents = []
for metrics in result:
agents.append({
"agent_wallet": metrics.agent_wallet,
"total_staked": metrics.total_staked,
"staker_count": metrics.staker_count,
"current_tier": metrics.current_tier,
"average_accuracy": metrics.average_accuracy,
"success_rate": metrics.success_rate,
"current_apy": await self.calculate_apy(metrics.agent_wallet, 30)
})
return agents
except Exception as e:
logger.error(f"Failed to get supported agents: {e}")
raise
async def get_staking_stats(self, period: str = "daily") -> Dict[str, Any]:
"""Get staking system statistics"""
try:
# Calculate time period
if period == "hourly":
start_date = datetime.utcnow() - timedelta(hours=1)
elif period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=1)
# Get total staked
total_staked_stmt = select(func.sum(AgentStake.amount)).where(
AgentStake.start_time >= start_date
)
total_staked = self.session.execute(total_staked_stmt).scalar() or 0.0
# Get active stakes
active_stakes_stmt = select(func.count(AgentStake.stake_id)).where(
and_(
AgentStake.start_time >= start_date,
AgentStake.status == StakeStatus.ACTIVE
)
)
active_stakes = self.session.execute(active_stakes_stmt).scalar() or 0
# Get unique stakers
unique_stakers_stmt = select(func.count(func.distinct(AgentStake.staker_address))).where(
AgentStake.start_time >= start_date
)
unique_stakers = self.session.execute(unique_stakers_stmt).scalar() or 0
# Get average APY
avg_apy_stmt = select(func.avg(AgentStake.current_apy)).where(
AgentStake.start_time >= start_date
)
avg_apy = self.session.execute(avg_apy_stmt).scalar() or 0.0
# Get total rewards
total_rewards_stmt = select(func.sum(AgentMetrics.total_rewards_distributed)).where(
AgentMetrics.last_update_time >= start_date
)
total_rewards = self.session.execute(total_rewards_stmt).scalar() or 0.0
# Get tier distribution
tier_stmt = select(
AgentStake.agent_tier,
func.count(AgentStake.stake_id).label('count')
).where(
AgentStake.start_time >= start_date
).group_by(AgentStake.agent_tier)
tier_result = self.session.execute(tier_stmt).all()
tier_distribution = {row.agent_tier.value: row.count for row in tier_result}
return {
"total_staked": total_staked,
"total_stakers": unique_stakers,
"active_stakes": active_stakes,
"average_apy": avg_apy,
"total_rewards_distributed": total_rewards,
"tier_distribution": tier_distribution
}
except Exception as e:
logger.error(f"Failed to get staking stats: {e}")
raise
async def get_leaderboard(
self,
period: str = "weekly",
metric: str = "total_staked",
limit: int = 50
) -> List[Dict[str, Any]]:
"""Get staking leaderboard"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(weeks=1)
if metric == "total_staked":
stmt = select(
AgentStake.agent_wallet,
func.sum(AgentStake.amount).label('total_staked'),
func.count(AgentStake.stake_id).label('stake_count')
).where(
AgentStake.start_time >= start_date
).group_by(AgentStake.agent_wallet).order_by(
func.sum(AgentStake.amount).desc()
).limit(limit)
elif metric == "total_rewards":
stmt = select(
AgentMetrics.agent_wallet,
AgentMetrics.total_rewards_distributed,
AgentMetrics.staker_count
).where(
AgentMetrics.last_update_time >= start_date
).order_by(
AgentMetrics.total_rewards_distributed.desc()
).limit(limit)
elif metric == "apy":
stmt = select(
AgentStake.agent_wallet,
func.avg(AgentStake.current_apy).label('avg_apy'),
func.count(AgentStake.stake_id).label('stake_count')
).where(
AgentStake.start_time >= start_date
).group_by(AgentStake.agent_wallet).order_by(
func.avg(AgentStake.current_apy).desc()
).limit(limit)
result = self.session.execute(stmt).all()
leaderboard = []
for row in result:
leaderboard.append({
"agent_wallet": row.agent_wallet,
"rank": len(leaderboard) + 1,
**row._asdict()
})
return leaderboard
except Exception as e:
logger.error(f"Failed to get leaderboard: {e}")
raise
async def get_user_rewards(
self,
user_address: str,
period: str = "monthly"
) -> Dict[str, Any]:
"""Get user's staking rewards"""
try:
# Calculate time period
if period == "daily":
start_date = datetime.utcnow() - timedelta(days=1)
elif period == "weekly":
start_date = datetime.utcnow() - timedelta(weeks=1)
elif period == "monthly":
start_date = datetime.utcnow() - timedelta(days=30)
else:
start_date = datetime.utcnow() - timedelta(days=30)
# Get user's stakes
stmt = select(AgentStake).where(
and_(
AgentStake.staker_address == user_address,
AgentStake.start_time >= start_date
)
)
stakes = self.session.execute(stmt).scalars().all()
total_rewards = 0.0
total_staked = 0.0
active_stakes = 0
for stake in stakes:
total_rewards += stake.accumulated_rewards
total_staked += stake.amount
if stake.status == StakeStatus.ACTIVE:
active_stakes += 1
return {
"user_address": user_address,
"period": period,
"total_rewards": total_rewards,
"total_staked": total_staked,
"active_stakes": active_stakes,
"average_apy": (total_rewards / total_staked * 100) if total_staked > 0 else 0.0
}
except Exception as e:
logger.error(f"Failed to get user rewards: {e}")
raise
async def claim_rewards(self, stake_ids: List[str]) -> Dict[str, Any]:
"""Claim accumulated rewards for multiple stakes"""
try:
total_rewards = 0.0
for stake_id in stake_ids:
stake = await self.get_stake(stake_id)
if not stake:
continue
total_rewards += stake.accumulated_rewards
stake.accumulated_rewards = 0.0
stake.last_reward_time = datetime.utcnow()
self.session.commit()
return {
"total_rewards": total_rewards,
"claimed_stakes": len(stake_ids)
}
except Exception as e:
logger.error(f"Failed to claim rewards: {e}")
self.session.rollback()
raise
async def get_risk_assessment(self, agent_wallet: str) -> Dict[str, Any]:
"""Get risk assessment for staking on an agent"""
try:
agent_metrics = await self.get_agent_metrics(agent_wallet)
if not agent_metrics:
raise ValueError("Agent not found")
# Calculate risk factors
risk_factors = {
"performance_risk": max(0, 100 - agent_metrics.average_accuracy) / 100,
"volatility_risk": 0.1 if agent_metrics.success_rate < 80 else 0.05,
"concentration_risk": min(1.0, agent_metrics.total_staked / 100000), # High concentration if >100k
"new_agent_risk": 0.2 if agent_metrics.total_submissions < 10 else 0.0
}
# Calculate overall risk score
risk_score = sum(risk_factors.values()) / len(risk_factors)
# Determine risk level
if risk_score < 0.2:
risk_level = "low"
elif risk_score < 0.5:
risk_level = "medium"
else:
risk_level = "high"
return {
"agent_wallet": agent_wallet,
"risk_score": risk_score,
"risk_level": risk_level,
"risk_factors": risk_factors,
"recommendations": self._get_risk_recommendations(risk_level, risk_factors)
}
except Exception as e:
logger.error(f"Failed to get risk assessment: {e}")
raise
# Private helper methods
async def _update_staking_pool(
self,
agent_wallet: str,
staker_address: str,
amount: float,
is_stake: bool
):
"""Update staking pool"""
try:
pool = await self.get_staking_pool(agent_wallet)
if not pool:
pool = StakingPool(agent_wallet=agent_wallet)
self.session.add(pool)
if is_stake:
if staker_address not in pool.active_stakers:
pool.active_stakers.append(staker_address)
pool.total_staked += amount
else:
pool.total_staked -= amount
if staker_address in pool.active_stakers:
pool.active_stakers.remove(staker_address)
# Update pool APY
if pool.total_staked > 0:
pool.pool_apy = await self.calculate_apy(agent_wallet, 30)
except Exception as e:
logger.error(f"Failed to update staking pool: {e}")
raise
async def _calculate_rewards(self, stake_id: str):
"""Calculate and update rewards for a stake"""
try:
stake = await self.get_stake(stake_id)
if not stake or stake.status != StakeStatus.ACTIVE:
return
time_elapsed = datetime.utcnow() - stake.last_reward_time
yearly_rewards = (stake.amount * stake.current_apy) / 100
current_rewards = (yearly_rewards * time_elapsed.total_seconds()) / (365 * 24 * 3600)
stake.accumulated_rewards += current_rewards
stake.last_reward_time = datetime.utcnow()
# Auto-compound if enabled
if stake.auto_compound and current_rewards >= 100.0:
stake.amount += current_rewards
stake.accumulated_rewards = 0.0
except Exception as e:
logger.error(f"Failed to calculate rewards: {e}")
raise
async def _calculate_agent_tier(self, agent_metrics: AgentMetrics) -> PerformanceTier:
"""Calculate agent performance tier"""
success_rate = agent_metrics.success_rate
accuracy = agent_metrics.average_accuracy
score = (accuracy * 0.6) + (success_rate * 0.4)
if score >= 95:
return PerformanceTier.DIAMOND
elif score >= 90:
return PerformanceTier.PLATINUM
elif score >= 80:
return PerformanceTier.GOLD
elif score >= 70:
return PerformanceTier.SILVER
else:
return PerformanceTier.BRONZE
async def _get_tier_score(self, tier: PerformanceTier) -> float:
"""Get score for a tier"""
tier_scores = {
PerformanceTier.DIAMOND: 95.0,
PerformanceTier.PLATINUM: 90.0,
PerformanceTier.GOLD: 80.0,
PerformanceTier.SILVER: 70.0,
PerformanceTier.BRONZE: 60.0
}
return tier_scores.get(tier, 60.0)
async def _update_stake_apy_for_agent(self, agent_wallet: str, new_tier: PerformanceTier):
"""Update APY for all active stakes on an agent"""
try:
stmt = select(AgentStake).where(
and_(
AgentStake.agent_wallet == agent_wallet,
AgentStake.status == StakeStatus.ACTIVE
)
)
stakes = self.session.execute(stmt).scalars().all()
for stake in stakes:
stake.current_apy = await self.calculate_apy(agent_wallet, stake.lock_period)
stake.agent_tier = new_tier
except Exception as e:
logger.error(f"Failed to update stake APY: {e}")
raise
def _get_risk_recommendations(self, risk_level: str, risk_factors: Dict[str, float]) -> List[str]:
"""Get risk recommendations based on risk level and factors"""
recommendations = []
if risk_level == "high":
recommendations.append("Consider staking a smaller amount")
recommendations.append("Monitor agent performance closely")
if risk_factors.get("performance_risk", 0) > 0.3:
recommendations.append("Agent has low accuracy - consider waiting for improvement")
if risk_factors.get("concentration_risk", 0) > 0.5:
recommendations.append("High concentration - diversify across multiple agents")
if risk_factors.get("new_agent_risk", 0) > 0.1:
recommendations.append("New agent - consider waiting for more performance data")
if not recommendations:
recommendations.append("Agent appears to be low risk for staking")
return recommendations