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