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

718
contracts/AgentBounty.sol Normal file
View File

@@ -0,0 +1,718 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./PerformanceVerifier.sol";
import "./AIToken.sol";
/**
* @title Agent Bounty System
* @dev Automated bounty board for AI agent capabilities with ZK-proof verification
* @notice Allows DAO and users to create bounties that are automatically completed when agents submit valid ZK-proofs
*/
contract AgentBounty is Ownable, ReentrancyGuard, Pausable {
// State variables
IERC20 public aitbcToken;
PerformanceVerifier public performanceVerifier;
uint256 public bountyCounter;
uint256 public creationFeePercentage = 50; // 0.5% in basis points
uint256 public successFeePercentage = 200; // 2% in basis points
uint256 public disputeFeePercentage = 10; // 0.1% in basis points
uint256 public platformFeePercentage = 100; // 1% in basis points
// Bounty tiers
enum BountyTier { BRONZE, SILVER, GOLD, PLATINUM }
// Bounty status
enum BountyStatus { CREATED, ACTIVE, SUBMITTED, VERIFIED, COMPLETED, EXPIRED, DISPUTED }
// Submission status
enum SubmissionStatus { PENDING, VERIFIED, REJECTED, DISPUTED }
// Structs
struct Bounty {
uint256 bountyId;
string title;
string description;
uint256 rewardAmount;
address creator;
BountyTier tier;
BountyStatus status;
bytes32 performanceCriteria; // Hash of performance requirements
uint256 minAccuracy;
uint256 deadline;
uint256 creationTime;
uint256 maxSubmissions;
uint256 submissionCount;
address winningSubmission;
bool requiresZKProof;
mapping(address => bool) authorizedSubmitters;
}
struct Submission {
uint256 submissionId;
uint256 bountyId;
address submitter;
bytes zkProof;
bytes32 performanceHash;
uint256 accuracy;
uint256 responseTime;
uint256 submissionTime;
SubmissionStatus status;
string disputeReason;
address verifier;
}
struct BountyStats {
uint256 totalBounties;
uint256 activeBounties;
uint256 completedBounties;
uint256 totalValueLocked;
uint256 averageReward;
uint256 successRate;
}
// Mappings
mapping(uint256 => Bounty) public bounties;
mapping(uint256 => Submission) public submissions;
mapping(uint256 => uint256[]) public bountySubmissions;
mapping(address => uint256[]) public userSubmissions;
mapping(address => uint256[]) public creatorBounties;
mapping(BountyTier => uint256) public tierRequirements;
mapping(uint256 => mapping(address => bool)) public hasSubmitted;
// Arrays
uint256[] public activeBountyIds;
address[] public authorizedCreators;
// Events
event BountyCreated(
uint256 indexed bountyId,
string title,
uint256 rewardAmount,
address indexed creator,
BountyTier tier,
uint256 deadline
);
event BountySubmitted(
uint256 indexed bountyId,
uint256 indexed submissionId,
address indexed submitter,
bytes32 performanceHash,
uint256 accuracy
);
event BountyVerified(
uint256 indexed bountyId,
uint256 indexed submissionId,
address indexed submitter,
bool success,
uint256 rewardAmount
);
event BountyCompleted(
uint256 indexed bountyId,
address indexed winner,
uint256 rewardAmount,
uint256 completionTime
);
event BountyExpired(
uint256 indexed bountyId,
uint256 refundAmount
);
event BountyDisputed(
uint256 indexed bountyId,
uint256 indexed submissionId,
address indexed disputer,
string reason
);
event PlatformFeeCollected(
uint256 indexed bountyId,
uint256 feeAmount,
address indexed collector
);
// Modifiers
modifier bountyExists(uint256 _bountyId) {
require(_bountyId < bountyCounter, "Bounty does not exist");
_;
}
modifier onlyAuthorizedCreator() {
require(isAuthorizedCreator(msg.sender), "Not authorized to create bounties");
_;
}
modifier validBountyStatus(uint256 _bountyId, BountyStatus _requiredStatus) {
require(bounties[_bountyId].status == _requiredStatus, "Invalid bounty status");
_;
}
modifier beforeDeadline(uint256 _deadline) {
require(block.timestamp <= _deadline, "Deadline passed");
_;
}
modifier sufficientBalance(uint256 _amount) {
require(aitbcToken.balanceOf(msg.sender) >= _amount, "Insufficient balance");
_;
}
constructor(address _aitbcToken, address _performanceVerifier) {
aitbcToken = IERC20(_aitbcToken);
performanceVerifier = PerformanceVerifier(_performanceVerifier);
// Set tier requirements (minimum reward amounts)
tierRequirements[BountyTier.BRONZE] = 100 * 10**18; // 100 AITBC
tierRequirements[BountyTier.SILVER] = 500 * 10**18; // 500 AITBC
tierRequirements[BountyTier.GOLD] = 1000 * 10**18; // 1000 AITBC
tierRequirements[BountyTier.PLATINUM] = 5000 * 10**18; // 5000 AITBC
}
/**
* @dev Creates a new bounty
* @param _title Bounty title
* @param _description Detailed description
* @param _rewardAmount Reward amount in AITBC tokens
* @param _tier Bounty tier
* @param _performanceCriteria Hash of performance requirements
* @param _minAccuracy Minimum accuracy required
* @param _deadline Bounty deadline
* @param _maxSubmissions Maximum number of submissions allowed
* @param _requiresZKProof Whether ZK-proof is required
*/
function createBounty(
string memory _title,
string memory _description,
uint256 _rewardAmount,
BountyTier _tier,
bytes32 _performanceCriteria,
uint256 _minAccuracy,
uint256 _deadline,
uint256 _maxSubmissions,
bool _requiresZKProof
) external
onlyAuthorizedCreator
sufficientBalance(_rewardAmount)
beforeDeadline(_deadline)
nonReentrant
returns (uint256)
{
require(_rewardAmount >= tierRequirements[_tier], "Reward below tier minimum");
require(_minAccuracy <= 100, "Invalid accuracy");
require(_maxSubmissions > 0, "Invalid max submissions");
require(_deadline > block.timestamp, "Invalid deadline");
uint256 bountyId = bountyCounter++;
Bounty storage bounty = bounties[bountyId];
bounty.bountyId = bountyId;
bounty.title = _title;
bounty.description = _description;
bounty.rewardAmount = _rewardAmount;
bounty.creator = msg.sender;
bounty.tier = _tier;
bounty.status = BountyStatus.CREATED;
bounty.performanceCriteria = _performanceCriteria;
bounty.minAccuracy = _minAccuracy;
bounty.deadline = _deadline;
bounty.creationTime = block.timestamp;
bounty.maxSubmissions = _maxSubmissions;
bounty.submissionCount = 0;
bounty.requiresZKProof = _requiresZKProof;
// Calculate and collect creation fee
uint256 creationFee = (_rewardAmount * creationFeePercentage) / 10000;
uint256 totalRequired = _rewardAmount + creationFee;
require(aitbcToken.balanceOf(msg.sender) >= totalRequired, "Insufficient total amount");
// Transfer tokens to contract
require(aitbcToken.transferFrom(msg.sender, address(this), totalRequired), "Transfer failed");
// Transfer creation fee to DAO treasury (owner for now)
if (creationFee > 0) {
require(aitbcToken.transfer(owner(), creationFee), "Fee transfer failed");
emit PlatformFeeCollected(bountyId, creationFee, owner());
}
// Update tracking arrays
activeBountyIds.push(bountyId);
creatorBounties[msg.sender].push(bountyId);
// Activate bounty
bounty.status = BountyStatus.ACTIVE;
emit BountyCreated(bountyId, _title, _rewardAmount, msg.sender, _tier, _deadline);
return bountyId;
}
/**
* @dev Submits a solution to a bounty
* @param _bountyId Bounty ID
* @param _zkProof Zero-knowledge proof (if required)
* @param _performanceHash Hash of performance metrics
* @param _accuracy Achieved accuracy
* @param _responseTime Response time in milliseconds
*/
function submitBountySolution(
uint256 _bountyId,
bytes memory _zkProof,
bytes32 _performanceHash,
uint256 _accuracy,
uint256 _responseTime
) external
bountyExists(_bountyId)
validBountyStatus(_bountyId, BountyStatus.ACTIVE)
beforeDeadline(bounties[_bountyId].deadline)
nonReentrant
returns (uint256)
{
Bounty storage bounty = bounties[_bountyId];
require(!hasSubmitted[_bountyId][msg.sender], "Already submitted");
require(bounty.submissionCount < bounty.maxSubmissions, "Max submissions reached");
if (bounty.requiresZKProof) {
require(_zkProof.length > 0, "ZK-proof required");
}
uint256 submissionId = bounty.submissionCount; // Use count as ID
Submission storage submission = submissions[submissionId];
submission.submissionId = submissionId;
submission.bountyId = _bountyId;
submission.submitter = msg.sender;
submission.zkProof = _zkProof;
submission.performanceHash = _performanceHash;
submission.accuracy = _accuracy;
submission.responseTime = _responseTime;
submission.submissionTime = block.timestamp;
submission.status = SubmissionStatus.PENDING;
// Update tracking
bounty.submissionCount++;
hasSubmitted[_bountyId][msg.sender] = true;
bountySubmissions[_bountyId].push(submissionId);
userSubmissions[msg.sender].push(submissionId);
// Auto-verify if ZK-proof is provided
if (_zkProof.length > 0) {
_verifySubmission(_bountyId, submissionId);
}
emit BountySubmitted(_bountyId, submissionId, msg.sender, _performanceHash, _accuracy);
return submissionId;
}
/**
* @dev Manually verifies a submission (oracle or automated)
* @param _bountyId Bounty ID
* @param _submissionId Submission ID
* @param _verified Whether the submission is verified
* @param _verifier Address of the verifier
*/
function verifySubmission(
uint256 _bountyId,
uint256 _submissionId,
bool _verified,
address _verifier
) external
bountyExists(_bountyId)
nonReentrant
{
Bounty storage bounty = bounties[_bountyId];
Submission storage submission = submissions[_submissionId];
require(submission.status == SubmissionStatus.PENDING, "Submission not pending");
require(submission.bountyId == _bountyId, "Submission bounty mismatch");
submission.status = _verified ? SubmissionStatus.VERIFIED : SubmissionStatus.REJECTED;
submission.verifier = _verifier;
if (_verified) {
// Check if this meets the bounty requirements
if (submission.accuracy >= bounty.minAccuracy) {
_completeBounty(_bountyId, _submissionId);
}
}
emit BountyVerified(_bountyId, _submissionId, submission.submitter, _verified, bounty.rewardAmount);
}
/**
* @dev Disputes a submission
* @param _bountyId Bounty ID
* @param _submissionId Submission ID
* @param _reason Reason for dispute
*/
function disputeSubmission(
uint256 _bountyId,
uint256 _submissionId,
string memory _reason
) external
bountyExists(_bountyId)
nonReentrant
{
Bounty storage bounty = bounties[_bountyId];
Submission storage submission = submissions[_submissionId];
require(submission.status == SubmissionStatus.VERIFIED, "Can only dispute verified submissions");
require(block.timestamp - submission.submissionTime <= 86400, "Dispute window expired"); // 24 hours
submission.status = SubmissionStatus.DISPUTED;
submission.disputeReason = _reason;
bounty.status = BountyStatus.DISPUTED;
// Collect dispute fee
uint256 disputeFee = (bounty.rewardAmount * disputeFeePercentage) / 10000;
if (disputeFee > 0) {
require(aitbcToken.transferFrom(msg.sender, address(this), disputeFee), "Dispute fee transfer failed");
}
emit BountyDisputed(_bountyId, _submissionId, msg.sender, _reason);
}
/**
* @dev Resolves a dispute
* @param _bountyId Bounty ID
* @param _submissionId Submission ID
* @param _upholdDispute Whether to uphold the dispute
*/
function resolveDispute(
uint256 _bountyId,
uint256 _submissionId,
bool _upholdDispute
) external onlyOwner bountyExists(_bountyId) nonReentrant {
Bounty storage bounty = bounties[_bountyId];
Submission storage submission = submissions[_submissionId];
require(bounty.status == BountyStatus.DISPUTED, "No dispute to resolve");
require(submission.status == SubmissionStatus.DISPUTED, "Submission not disputed");
if (_upholdDispute) {
// Reject the submission
submission.status = SubmissionStatus.REJECTED;
bounty.status = BountyStatus.ACTIVE;
// Return dispute fee
uint256 disputeFee = (bounty.rewardAmount * disputeFeePercentage) / 10000;
if (disputeFee > 0) {
require(aitbcToken.transfer(msg.sender, disputeFee), "Dispute fee return failed");
}
} else {
// Uphold the submission
submission.status = SubmissionStatus.VERIFIED;
_completeBounty(_bountyId, _submissionId);
}
}
/**
* @dev Expires a bounty and returns funds to creator
* @param _bountyId Bounty ID
*/
function expireBounty(uint256 _bountyId) external bountyExists(_bountyId) nonReentrant {
Bounty storage bounty = bounties[_bountyId];
require(bounty.status == BountyStatus.ACTIVE, "Bounty not active");
require(block.timestamp > bounty.deadline, "Deadline not passed");
bounty.status = BountyStatus.EXPIRED;
// Return funds to creator
uint256 refundAmount = bounty.rewardAmount;
require(aitbcToken.transfer(bounty.creator, refundAmount), "Refund transfer failed");
// Remove from active bounties
_removeFromActiveBounties(_bountyId);
emit BountyExpired(_bountyId, refundAmount);
}
/**
* @dev Authorizes a creator to create bounties
* @param _creator Address to authorize
*/
function authorizeCreator(address _creator) external onlyOwner {
require(_creator != address(0), "Invalid address");
require(!isAuthorizedCreator(_creator), "Already authorized");
authorizedCreators.push(_creator);
bounties[0].authorizedSubmitters[_creator] = true; // Use bounty 0 as storage
}
/**
* @dev Revokes creator authorization
* @param _creator Address to revoke
*/
function revokeCreator(address _creator) external onlyOwner {
require(isAuthorizedCreator(_creator), "Not authorized");
bounties[0].authorizedSubmitters[_creator] = false; // Use bounty 0 as storage
// Remove from array
for (uint256 i = 0; i < authorizedCreators.length; i++) {
if (authorizedCreators[i] == _creator) {
authorizedCreators[i] = authorizedCreators[authorizedCreators.length - 1];
authorizedCreators.pop();
break;
}
}
}
/**
* @dev Updates fee percentages
* @param _creationFee New creation fee percentage
* @param _successFee New success fee percentage
* @param _platformFee New platform fee percentage
*/
function updateFees(
uint256 _creationFee,
uint256 _successFee,
uint256 _platformFee
) external onlyOwner {
require(_creationFee <= 500, "Creation fee too high"); // Max 5%
require(_successFee <= 500, "Success fee too high"); // Max 5%
require(_platformFee <= 500, "Platform fee too high"); // Max 5%
creationFeePercentage = _creationFee;
successFeePercentage = _successFee;
platformFeePercentage = _platformFee;
}
/**
* @dev Updates tier requirements
* @param _tier Bounty tier
* @param _minimumReward New minimum reward
*/
function updateTierRequirement(BountyTier _tier, uint256 _minimumReward) external onlyOwner {
tierRequirements[_tier] = _minimumReward;
}
// View functions
/**
* @dev Gets bounty details
* @param _bountyId Bounty ID
*/
function getBounty(uint256 _bountyId) external view bountyExists(_bountyId) returns (
string memory title,
string memory description,
uint256 rewardAmount,
address creator,
BountyTier tier,
BountyStatus status,
bytes32 performanceCriteria,
uint256 minAccuracy,
uint256 deadline,
uint256 creationTime,
uint256 maxSubmissions,
uint256 submissionCount,
bool requiresZKProof
) {
Bounty storage bounty = bounties[_bountyId];
return (
bounty.title,
bounty.description,
bounty.rewardAmount,
bounty.creator,
bounty.tier,
bounty.status,
bounty.performanceCriteria,
bounty.minAccuracy,
bounty.deadline,
bounty.creationTime,
bounty.maxSubmissions,
bounty.submissionCount,
bounty.requiresZKProof
);
}
/**
* @dev Gets submission details
* @param _submissionId Submission ID
*/
function getSubmission(uint256 _submissionId) external view returns (
uint256 bountyId,
address submitter,
bytes32 performanceHash,
uint256 accuracy,
uint256 responseTime,
uint256 submissionTime,
SubmissionStatus status,
address verifier
) {
Submission storage submission = submissions[_submissionId];
return (
submission.bountyId,
submission.submitter,
submission.performanceHash,
submission.accuracy,
submission.responseTime,
submission.submissionTime,
submission.status,
submission.verifier
);
}
/**
* @dev Gets all submissions for a bounty
* @param _bountyId Bounty ID
*/
function getBountySubmissions(uint256 _bountyId) external view bountyExists(_bountyId) returns (uint256[] memory) {
return bountySubmissions[_bountyId];
}
/**
* @dev Gets all bounties created by a user
* @param _creator Creator address
*/
function getCreatorBounties(address _creator) external view returns (uint256[] memory) {
return creatorBounties[_creator];
}
/**
* @dev Gets all submissions by a user
* @param _submitter Submitter address
*/
function getUserSubmissions(address _submitter) external view returns (uint256[] memory) {
return userSubmissions[_submitter];
}
/**
* @dev Gets all active bounty IDs
*/
function getActiveBounties() external view returns (uint256[] memory) {
return activeBountyIds;
}
/**
* @dev Gets bounty statistics
*/
function getBountyStats() external view returns (BountyStats memory) {
uint256 totalValue = 0;
uint256 activeCount = 0;
uint256 completedCount = 0;
for (uint256 i = 0; i < bountyCounter; i++) {
if (bounties[i].status == BountyStatus.ACTIVE) {
activeCount++;
totalValue += bounties[i].rewardAmount;
} else if (bounties[i].status == BountyStatus.COMPLETED) {
completedCount++;
totalValue += bounties[i].rewardAmount;
}
}
uint256 avgReward = bountyCounter > 0 ? totalValue / bountyCounter : 0;
uint256 successRate = completedCount > 0 ? (completedCount * 100) / bountyCounter : 0;
return BountyStats({
totalBounties: bountyCounter,
activeBounties: activeCount,
completedBounties: completedCount,
totalValueLocked: totalValue,
averageReward: avgReward,
successRate: successRate
});
}
/**
* @dev Checks if an address is authorized to create bounties
* @param _creator Address to check
*/
function isAuthorizedCreator(address _creator) public view returns (bool) {
return bounties[0].authorizedSubmitters[_creator]; // Use bounty 0 as storage
}
// Internal functions
function _verifySubmission(uint256 _bountyId, uint256 _submissionId) internal {
Bounty storage bounty = bounties[_bountyId];
Submission storage submission = submissions[_submissionId];
// Verify ZK-proof using PerformanceVerifier
bool proofValid = performanceVerifier.verifyPerformanceProof(
0, // Use dummy agreement ID for bounty verification
submission.responseTime,
submission.accuracy,
95, // Default availability
100, // Default compute power
submission.zkProof
);
if (proofValid && submission.accuracy >= bounty.minAccuracy) {
submission.status = SubmissionStatus.VERIFIED;
_completeBounty(_bountyId, _submissionId);
} else {
submission.status = SubmissionStatus.REJECTED;
}
}
function _completeBounty(uint256 _bountyId, uint256 _submissionId) internal {
Bounty storage bounty = bounties[_bountyId];
Submission storage submission = submissions[_submissionId];
require(bounty.status == BountyStatus.ACTIVE || bounty.status == BountyStatus.SUBMITTED, "Bounty not active");
bounty.status = BountyStatus.COMPLETED;
bounty.winningSubmission = submission.submitter;
// Calculate fees
uint256 successFee = (bounty.rewardAmount * successFeePercentage) / 10000;
uint256 platformFee = (bounty.rewardAmount * platformFeePercentage) / 10000;
uint256 totalFees = successFee + platformFee;
uint256 winnerReward = bounty.rewardAmount - totalFees;
// Transfer reward to winner
if (winnerReward > 0) {
require(aitbcToken.transfer(submission.submitter, winnerReward), "Reward transfer failed");
}
// Transfer fees to treasury
if (totalFees > 0) {
require(aitbcToken.transfer(owner(), totalFees), "Fee transfer failed");
emit PlatformFeeCollected(_bountyId, totalFees, owner());
}
// Remove from active bounties
_removeFromActiveBounties(_bountyId);
emit BountyCompleted(_bountyId, submission.submitter, winnerReward, block.timestamp);
}
function _removeFromActiveBounties(uint256 _bountyId) internal {
for (uint256 i = 0; i < activeBountyIds.length; i++) {
if (activeBountyIds[i] == _bountyId) {
activeBountyIds[i] = activeBountyIds[activeBountyIds.length - 1];
activeBountyIds.pop();
break;
}
}
}
/**
* @dev Emergency pause function
*/
function pause() external onlyOwner {
_pause();
}
/**
* @dev Unpause function
*/
function unpause() external onlyOwner {
_unpause();
}
}

827
contracts/AgentStaking.sol Normal file
View File

@@ -0,0 +1,827 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./PerformanceVerifier.sol";
import "./AIToken.sol";
/**
* @title Agent Staking System
* @dev Reputation-based yield farming for AI agents with dynamic APY calculation
* @notice Allows users to stake AITBC tokens on agent wallets and earn rewards based on agent performance
*/
contract AgentStaking is Ownable, ReentrancyGuard, Pausable {
// State variables
IERC20 public aitbcToken;
PerformanceVerifier public performanceVerifier;
uint256 public stakeCounter;
uint256 public baseAPY = 500; // 5% base APY in basis points
uint256 public maxAPY = 2000; // 20% max APY in basis points
uint256 public minStakeAmount = 100 * 10**18; // 100 AITBC minimum
uint256 public maxStakeAmount = 100000 * 10**18; // 100k AITBC maximum
uint256 public unbondingPeriod = 7 days;
uint256 public rewardDistributionInterval = 1 days;
uint256 public platformFeePercentage = 100; // 1% platform fee
uint256 public earlyUnbondPenalty = 1000; // 10% penalty for early unbonding
// Staking status
enum StakeStatus { ACTIVE, UNBONDING, COMPLETED, SLASHED }
// Agent performance tier
enum PerformanceTier { BRONZE, SILVER, GOLD, PLATINUM, DIAMOND }
// Structs
struct Stake {
uint256 stakeId;
address staker;
address agentWallet;
uint256 amount;
uint256 lockPeriod;
uint256 startTime;
uint256 endTime;
StakeStatus status;
uint256 accumulatedRewards;
uint256 lastRewardTime;
uint256 currentAPY;
PerformanceTier agentTier;
bool autoCompound;
}
struct AgentMetrics {
address agentWallet;
uint256 totalStaked;
uint256 stakerCount;
uint256 totalRewardsDistributed;
uint256 averageAccuracy;
uint256 totalSubmissions;
uint256 successfulSubmissions;
uint256 lastUpdateTime;
PerformanceTier currentTier;
uint256 tierScore;
}
struct StakingPool {
address agentWallet;
uint256 totalStaked;
uint256 totalRewards;
uint256 poolAPY;
uint256 lastDistributionTime;
mapping(address => uint256) stakerShares;
address[] stakers;
}
struct RewardCalculation {
uint256 baseRewards;
uint256 performanceBonus;
uint256 lockBonus;
uint256 tierBonus;
uint256 totalRewards;
uint256 platformFee;
}
// Mappings
mapping(uint256 => Stake) public stakes;
mapping(address => uint256[]) public stakerStakes;
mapping(address => uint256[]) public agentStakes;
mapping(address => AgentMetrics) public agentMetrics;
mapping(address => StakingPool) public stakingPools;
mapping(PerformanceTier => uint256) public tierMultipliers;
mapping(uint256 => uint256) public lockPeriodMultipliers;
// Arrays
address[] public supportedAgents;
uint256[] public activeStakeIds;
// Events
event StakeCreated(
uint256 indexed stakeId,
address indexed staker,
address indexed agentWallet,
uint256 amount,
uint256 lockPeriod,
uint256 apy
);
event StakeUpdated(
uint256 indexed stakeId,
uint256 newAmount,
uint256 newAPY
);
event RewardsDistributed(
uint256 indexed stakeId,
address indexed staker,
uint256 rewardAmount,
uint256 platformFee
);
event StakeUnbonded(
uint256 indexed stakeId,
address indexed staker,
uint256 amount,
uint256 penalty
);
event StakeCompleted(
uint256 indexed stakeId,
address indexed staker,
uint256 totalAmount,
uint256 totalRewards
);
event AgentTierUpdated(
address indexed agentWallet,
PerformanceTier oldTier,
PerformanceTier newTier,
uint256 tierScore
);
event PoolRewardsDistributed(
address indexed agentWallet,
uint256 totalRewards,
uint256 stakerCount
);
event PlatformFeeCollected(
uint256 indexed stakeId,
uint256 feeAmount,
address indexed collector
);
// Modifiers
modifier stakeExists(uint256 _stakeId) {
require(_stakeId < stakeCounter, "Stake does not exist");
_;
}
modifier onlyStakeOwner(uint256 _stakeId) {
require(stakes[_stakeId].staker == msg.sender, "Not stake owner");
_;
}
modifier supportedAgent(address _agentWallet) {
require(agentMetrics[_agentWallet].agentWallet != address(0) || _agentWallet == address(0), "Agent not supported");
_;
}
modifier validStakeAmount(uint256 _amount) {
require(_amount >= minStakeAmount && _amount <= maxStakeAmount, "Invalid stake amount");
_;
}
modifier sufficientBalance(uint256 _amount) {
require(aitbcToken.balanceOf(msg.sender) >= _amount, "Insufficient balance");
_;
}
constructor(address _aitbcToken, address _performanceVerifier) {
aitbcToken = IERC20(_aitbcToken);
performanceVerifier = PerformanceVerifier(_performanceVerifier);
// Set tier multipliers (in basis points)
tierMultipliers[PerformanceTier.BRONZE] = 1000; // 1x
tierMultipliers[PerformanceTier.SILVER] = 1200; // 1.2x
tierMultipliers[PerformanceTier.GOLD] = 1500; // 1.5x
tierMultipliers[PerformanceTier.PLATINUM] = 2000; // 2x
tierMultipliers[PerformanceTier.DIAMOND] = 3000; // 3x
// Set lock period multipliers
lockPeriodMultipliers[30 days] = 1100; // 1.1x for 30 days
lockPeriodMultipliers[90 days] = 1250; // 1.25x for 90 days
lockPeriodMultipliers[180 days] = 1500; // 1.5x for 180 days
lockPeriodMultipliers[365 days] = 2000; // 2x for 365 days
}
/**
* @dev Creates a new stake on an agent wallet
* @param _agentWallet Address of the agent wallet
* @param _amount Amount to stake
* @param _lockPeriod Lock period in seconds
* @param _autoCompound Whether to auto-compound rewards
*/
function stakeOnAgent(
address _agentWallet,
uint256 _amount,
uint256 _lockPeriod,
bool _autoCompound
) external
supportedAgent(_agentWallet)
validStakeAmount(_amount)
sufficientBalance(_amount)
nonReentrant
returns (uint256)
{
require(_lockPeriod >= 1 days, "Lock period too short");
require(_lockPeriod <= 365 days, "Lock period too long");
uint256 stakeId = stakeCounter++;
// Calculate initial APY
PerformanceTier agentTier = _getAgentTier(_agentWallet);
uint256 apy = _calculateAPY(_agentWallet, _lockPeriod, agentTier);
Stake storage stake = stakes[stakeId];
stake.stakeId = stakeId;
stake.staker = msg.sender;
stake.agentWallet = _agentWallet;
stake.amount = _amount;
stake.lockPeriod = _lockPeriod;
stake.startTime = block.timestamp;
stake.endTime = block.timestamp + _lockPeriod;
stake.status = StakeStatus.ACTIVE;
stake.accumulatedRewards = 0;
stake.lastRewardTime = block.timestamp;
stake.currentAPY = apy;
stake.agentTier = agentTier;
stake.autoCompound = _autoCompound;
// Update agent metrics
_updateAgentMetrics(_agentWallet, _amount, true);
// Update staking pool
_updateStakingPool(_agentWallet, msg.sender, _amount, true);
// Update tracking arrays
stakerStakes[msg.sender].push(stakeId);
agentStakes[_agentWallet].push(stakeId);
activeStakeIds.push(stakeId);
// Transfer tokens to contract
require(aitbcToken.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
emit StakeCreated(stakeId, msg.sender, _agentWallet, _amount, _lockPeriod, apy);
return stakeId;
}
/**
* @dev Adds more tokens to an existing stake
* @param _stakeId Stake ID
* @param _additionalAmount Additional amount to stake
*/
function addToStake(
uint256 _stakeId,
uint256 _additionalAmount
) external
stakeExists(_stakeId)
onlyStakeOwner(_stakeId)
validStakeAmount(_additionalAmount)
sufficientBalance(_additionalAmount)
nonReentrant
{
Stake storage stake = stakes[_stakeId];
require(stake.status == StakeStatus.ACTIVE, "Stake not active");
// Calculate new APY
uint256 newTotalAmount = stake.amount + _additionalAmount;
uint256 newAPY = _calculateAPY(stake.agentWallet, stake.lockPeriod, stake.agentTier);
// Update stake
stake.amount = newTotalAmount;
stake.currentAPY = newAPY;
// Update agent metrics
_updateAgentMetrics(stake.agentWallet, _additionalAmount, true);
// Update staking pool
_updateStakingPool(stake.agentWallet, msg.sender, _additionalAmount, true);
// Transfer additional tokens
require(aitbcToken.transferFrom(msg.sender, address(this), _additionalAmount), "Transfer failed");
emit StakeUpdated(_stakeId, newTotalAmount, newAPY);
}
/**
* @dev Initiates unbonding for a stake
* @param _stakeId Stake ID
*/
function unbondStake(uint256 _stakeId) external
stakeExists(_stakeId)
onlyStakeOwner(_stakeId)
nonReentrant
{
Stake storage stake = stakes[_stakeId];
require(stake.status == StakeStatus.ACTIVE, "Stake not active");
require(block.timestamp >= stake.endTime, "Lock period not ended");
// Calculate final rewards
_calculateRewards(_stakeId);
stake.status = StakeStatus.UNBONDING;
// Remove from active stakes
_removeFromActiveStakes(_stakeId);
}
/**
* @dev Completes unbonding and returns stake + rewards
* @param _stakeId Stake ID
*/
function completeUnbonding(uint256 _stakeId) external
stakeExists(_stakeId)
onlyStakeOwner(_stakeId)
nonReentrant
{
Stake storage stake = stakes[_stakeId];
require(stake.status == StakeStatus.UNBONDING, "Stake not unbonding");
require(block.timestamp >= stake.endTime + unbondingPeriod, "Unbonding period not ended");
uint256 totalAmount = stake.amount;
uint256 totalRewards = stake.accumulatedRewards;
// Apply early unbonding penalty if applicable
uint256 penalty = 0;
if (block.timestamp < stake.endTime + 30 days) {
penalty = (totalAmount * earlyUnbondPenalty) / 10000;
totalAmount -= penalty;
}
stake.status = StakeStatus.COMPLETED;
// Update agent metrics
_updateAgentMetrics(stake.agentWallet, stake.amount, false);
// Update staking pool
_updateStakingPool(stake.agentWallet, msg.sender, stake.amount, false);
// Transfer tokens back to staker
if (totalAmount > 0) {
require(aitbcToken.transfer(msg.sender, totalAmount), "Stake transfer failed");
}
if (totalRewards > 0) {
require(aitbcToken.transfer(msg.sender, totalRewards), "Rewards transfer failed");
}
emit StakeCompleted(_stakeId, msg.sender, totalAmount, totalRewards);
emit StakeUnbonded(_stakeId, msg.sender, totalAmount, penalty);
}
/**
* @dev Distributes agent earnings to stakers
* @param _agentWallet Agent wallet address
* @param _totalEarnings Total earnings to distribute
*/
function distributeAgentEarnings(
address _agentWallet,
uint256 _totalEarnings
) external
supportedAgent(_agentWallet)
nonReentrant
{
require(_totalEarnings > 0, "No earnings to distribute");
StakingPool storage pool = stakingPools[_agentWallet];
require(pool.totalStaked > 0, "No stakers in pool");
// Calculate platform fee
uint256 platformFee = (_totalEarnings * platformFeePercentage) / 10000;
uint256 distributableAmount = _totalEarnings - platformFee;
// Transfer platform fee
if (platformFee > 0) {
require(aitbcToken.transferFrom(msg.sender, owner(), platformFee), "Platform fee transfer failed");
}
// Transfer distributable amount to contract
require(aitbcToken.transferFrom(msg.sender, address(this), distributableAmount), "Earnings transfer failed");
// Distribute to stakers proportionally
uint256 totalDistributed = 0;
for (uint256 i = 0; i < pool.stakers.length; i++) {
address staker = pool.stakers[i];
uint256 stakerShare = pool.stakerShares[staker];
uint256 stakerReward = (distributableAmount * stakerShare) / pool.totalStaked;
if (stakerReward > 0) {
// Find and update all stakes for this staker on this agent
uint256[] storage stakesForAgent = agentStakes[_agentWallet];
for (uint256 j = 0; j < stakesForAgent.length; j++) {
uint256 stakeId = stakesForAgent[j];
Stake storage stake = stakes[stakeId];
if (stake.staker == staker && stake.status == StakeStatus.ACTIVE) {
stake.accumulatedRewards += stakerReward;
break;
}
}
totalDistributed += stakerReward;
}
}
// Update agent metrics
agentMetrics[_agentWallet].totalRewardsDistributed += totalDistributed;
emit PoolRewardsDistributed(_agentWallet, totalDistributed, pool.stakers.length);
}
/**
* @dev Updates agent performance metrics and tier
* @param _agentWallet Agent wallet address
* @param _accuracy Latest accuracy score
* @param _successful Whether the submission was successful
*/
function updateAgentPerformance(
address _agentWallet,
uint256 _accuracy,
bool _successful
) external
supportedAgent(_agentWallet)
nonReentrant
{
AgentMetrics storage metrics = agentMetrics[_agentWallet];
metrics.totalSubmissions++;
if (_successful) {
metrics.successfulSubmissions++;
}
// Update average accuracy (weighted average)
uint256 totalAccuracy = metrics.averageAccuracy * (metrics.totalSubmissions - 1) + _accuracy;
metrics.averageAccuracy = totalAccuracy / metrics.totalSubmissions;
metrics.lastUpdateTime = block.timestamp;
// Calculate new tier
PerformanceTier newTier = _calculateAgentTier(_agentWallet);
PerformanceTier oldTier = metrics.currentTier;
if (newTier != oldTier) {
metrics.currentTier = newTier;
// Update APY for all active stakes on this agent
uint256[] storage stakesForAgent = agentStakes[_agentWallet];
for (uint256 i = 0; i < stakesForAgent.length; i++) {
uint256 stakeId = stakesForAgent[i];
Stake storage stake = stakes[stakeId];
if (stake.status == StakeStatus.ACTIVE) {
stake.currentAPY = _calculateAPY(_agentWallet, stake.lockPeriod, newTier);
stake.agentTier = newTier;
}
}
emit AgentTierUpdated(_agentWallet, oldTier, newTier, metrics.tierScore);
}
}
/**
* @dev Adds a supported agent
* @param _agentWallet Agent wallet address
* @param _initialTier Initial performance tier
*/
function addSupportedAgent(
address _agentWallet,
PerformanceTier _initialTier
) external onlyOwner {
require(_agentWallet != address(0), "Invalid agent address");
require(agentMetrics[_agentWallet].agentWallet == address(0), "Agent already supported");
agentMetrics[_agentWallet] = AgentMetrics({
agentWallet: _agentWallet,
totalStaked: 0,
stakerCount: 0,
totalRewardsDistributed: 0,
averageAccuracy: 0,
totalSubmissions: 0,
successfulSubmissions: 0,
lastUpdateTime: block.timestamp,
currentTier: _initialTier,
tierScore: _getTierScore(_initialTier)
});
// Initialize staking pool
stakingPools[_agentWallet].agentWallet = _agentWallet;
stakingPools[_agentWallet].totalStaked = 0;
stakingPools[_agentWallet].totalRewards = 0;
stakingPools[_agentWallet].poolAPY = baseAPY;
stakingPools[_agentWallet].lastDistributionTime = block.timestamp;
supportedAgents.push(_agentWallet);
}
/**
* @dev Removes a supported agent
* @param _agentWallet Agent wallet address
*/
function removeSupportedAgent(address _agentWallet) external onlyOwner {
require(agentMetrics[_agentWallet].agentWallet != address(0), "Agent not supported");
require(agentMetrics[_agentWallet].totalStaked == 0, "Agent has active stakes");
// Remove from supported agents
for (uint256 i = 0; i < supportedAgents.length; i++) {
if (supportedAgents[i] == _agentWallet) {
supportedAgents[i] = supportedAgents[supportedAgents.length - 1];
supportedAgents.pop();
break;
}
}
delete agentMetrics[_agentWallet];
delete stakingPools[_agentWallet];
}
/**
* @dev Updates configuration parameters
* @param _baseAPY New base APY
* @param _maxAPY New maximum APY
* @param _platformFee New platform fee percentage
*/
function updateConfiguration(
uint256 _baseAPY,
uint256 _maxAPY,
uint256 _platformFee
) external onlyOwner {
require(_baseAPY <= _maxAPY, "Base APY cannot exceed max APY");
require(_maxAPY <= 5000, "Max APY too high"); // Max 50%
require(_platformFee <= 500, "Platform fee too high"); // Max 5%
baseAPY = _baseAPY;
maxAPY = _maxAPY;
platformFeePercentage = _platformFee;
}
// View functions
/**
* @dev Gets stake details
* @param _stakeId Stake ID
*/
function getStake(uint256 _stakeId) external view stakeExists(_stakeId) returns (
address staker,
address agentWallet,
uint256 amount,
uint256 lockPeriod,
uint256 startTime,
uint256 endTime,
StakeStatus status,
uint256 accumulatedRewards,
uint256 currentAPY,
PerformanceTier agentTier,
bool autoCompound
) {
Stake storage stake = stakes[_stakeId];
return (
stake.staker,
stake.agentWallet,
stake.amount,
stake.lockPeriod,
stake.startTime,
stake.endTime,
stake.status,
stake.accumulatedRewards,
stake.currentAPY,
stake.agentTier,
stake.autoCompound
);
}
/**
* @dev Gets agent metrics
* @param _agentWallet Agent wallet address
*/
function getAgentMetrics(address _agentWallet) external view returns (
uint256 totalStaked,
uint256 stakerCount,
uint256 totalRewardsDistributed,
uint256 averageAccuracy,
uint256 totalSubmissions,
uint256 successfulSubmissions,
PerformanceTier currentTier,
uint256 tierScore
) {
AgentMetrics storage metrics = agentMetrics[_agentWallet];
return (
metrics.totalStaked,
metrics.stakerCount,
metrics.totalRewardsDistributed,
metrics.averageAccuracy,
metrics.totalSubmissions,
metrics.successfulSubmissions,
metrics.currentTier,
metrics.tierScore
);
}
/**
* @dev Gets staking pool information
* @param _agentWallet Agent wallet address
*/
function getStakingPool(address _agentWallet) external view returns (
uint256 totalStaked,
uint256 totalRewards,
uint256 poolAPY,
uint256 stakerCount
) {
StakingPool storage pool = stakingPools[_agentWallet];
return (
pool.totalStaked,
pool.totalRewards,
pool.poolAPY,
pool.stakers.length
);
}
/**
* @dev Calculates current rewards for a stake
* @param _stakeId Stake ID
*/
function calculateRewards(uint256 _stakeId) external view stakeExists(_stakeId) returns (uint256) {
Stake storage stake = stakes[_stakeId];
if (stake.status != StakeStatus.ACTIVE) {
return stake.accumulatedRewards;
}
uint256 timeElapsed = block.timestamp - stake.lastRewardTime;
uint256 yearlyRewards = (stake.amount * stake.currentAPY) / 10000;
uint256 currentRewards = (yearlyRewards * timeElapsed) / 365 days;
return stake.accumulatedRewards + currentRewards;
}
/**
* @dev Gets all stakes for a staker
* @param _staker Staker address
*/
function getStakerStakes(address _staker) external view returns (uint256[] memory) {
return stakerStakes[_staker];
}
/**
* @dev Gets all stakes for an agent
* @param _agentWallet Agent wallet address
*/
function getAgentStakes(address _agentWallet) external view returns (uint256[] memory) {
return agentStakes[_agentWallet];
}
/**
* @dev Gets all supported agents
*/
function getSupportedAgents() external view returns (address[] memory) {
return supportedAgents;
}
/**
* @dev Gets all active stake IDs
*/
function getActiveStakes() external view returns (uint256[] memory) {
return activeStakeIds;
}
/**
* @dev Calculates APY for a stake
* @param _agentWallet Agent wallet address
* @param _lockPeriod Lock period
* @param _agentTier Agent performance tier
*/
function calculateAPY(
address _agentWallet,
uint256 _lockPeriod,
PerformanceTier _agentTier
) external view returns (uint256) {
return _calculateAPY(_agentWallet, _lockPeriod, _agentTier);
}
// Internal functions
function _calculateAPY(
address _agentWallet,
uint256 _lockPeriod,
PerformanceTier _agentTier
) internal view returns (uint256) {
uint256 tierMultiplier = tierMultipliers[_agentTier];
uint256 lockMultiplier = lockPeriodMultipliers[_lockPeriod];
uint256 apy = (baseAPY * tierMultiplier * lockMultiplier) / (10000 * 10000);
// Cap at maximum APY
return apy > maxAPY ? maxAPY : apy;
}
function _calculateRewards(uint256 _stakeId) internal {
Stake storage stake = stakes[_stakeId];
if (stake.status != StakeStatus.ACTIVE) {
return;
}
uint256 timeElapsed = block.timestamp - stake.lastRewardTime;
uint256 yearlyRewards = (stake.amount * stake.currentAPY) / 10000;
uint256 currentRewards = (yearlyRewards * timeElapsed) / 365 days;
stake.accumulatedRewards += currentRewards;
stake.lastRewardTime = block.timestamp;
// Auto-compound if enabled
if (stake.autoCompound && currentRewards >= minStakeAmount) {
stake.amount += currentRewards;
stake.accumulatedRewards = 0;
}
}
function _getAgentTier(address _agentWallet) internal view returns (PerformanceTier) {
AgentMetrics storage metrics = agentMetrics[_agentWallet];
return metrics.currentTier;
}
function _calculateAgentTier(address _agentWallet) internal view returns (PerformanceTier) {
AgentMetrics storage metrics = agentMetrics[_agentWallet];
uint256 successRate = metrics.totalSubmissions > 0 ?
(metrics.successfulSubmissions * 100) / metrics.totalSubmissions : 0;
uint256 score = (metrics.averageAccuracy * 50) / 100 + (successRate * 50) / 100;
if (score >= 95) return PerformanceTier.DIAMOND;
if (score >= 90) return PerformanceTier.PLATINUM;
if (score >= 80) return PerformanceTier.GOLD;
if (score >= 70) return PerformanceTier.SILVER;
return PerformanceTier.BRONZE;
}
function _getTierScore(PerformanceTier _tier) internal pure returns (uint256) {
if (_tier == PerformanceTier.DIAMOND) return 95;
if (_tier == PerformanceTier.PLATINUM) return 90;
if (_tier == PerformanceTier.GOLD) return 80;
if (_tier == PerformanceTier.SILVER) return 70;
return 60;
}
function _updateAgentMetrics(address _agentWallet, uint256 _amount, bool _isStake) internal {
AgentMetrics storage metrics = agentMetrics[_agentWallet];
if (_isStake) {
metrics.totalStaked += _amount;
if (metrics.totalStaked == _amount) {
metrics.stakerCount = 1;
}
} else {
metrics.totalStaked -= _amount;
if (metrics.totalStaked == 0) {
metrics.stakerCount = 0;
}
}
metrics.currentTier = _calculateAgentTier(_agentWallet);
metrics.tierScore = _getTierScore(metrics.currentTier);
}
function _updateStakingPool(address _agentWallet, address _staker, uint256 _amount, bool _isStake) internal {
StakingPool storage pool = stakingPools[_agentWallet];
if (_isStake) {
if (pool.stakerShares[_staker] == 0) {
pool.stakers.push(_staker);
}
pool.stakerShares[_staker] += _amount;
pool.totalStaked += _amount;
} else {
pool.stakerShares[_staker] -= _amount;
pool.totalStaked -= _amount;
// Remove staker from array if no shares left
if (pool.stakerShares[_staker] == 0) {
for (uint256 i = 0; i < pool.stakers.length; i++) {
if (pool.stakers[i] == _staker) {
pool.stakers[i] = pool.stakers[pool.stakers.length - 1];
pool.stakers.pop();
break;
}
}
}
}
// Update pool APY
if (pool.totalStaked > 0) {
pool.poolAPY = _calculateAPY(_agentWallet, 30 days, agentMetrics[_agentWallet].currentTier);
}
}
function _removeFromActiveStakes(uint256 _stakeId) internal {
for (uint256 i = 0; i < activeStakeIds.length; i++) {
if (activeStakeIds[i] == _stakeId) {
activeStakeIds[i] = activeStakeIds[activeStakeIds.length - 1];
activeStakeIds.pop();
break;
}
}
}
/**
* @dev Emergency pause function
*/
function pause() external onlyOwner {
_pause();
}
/**
* @dev Unpause function
*/
function unpause() external onlyOwner {
_unpause();
}
}

View File

@@ -0,0 +1,616 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./AgentBounty.sol";
import "./AgentStaking.sol";
import "./PerformanceVerifier.sol";
import "./AIToken.sol";
/**
* @title Bounty Integration Layer
* @dev Bridges PerformanceVerifier with bounty and staking contracts
* @notice Handles automatic bounty completion detection and cross-contract event handling
*/
contract BountyIntegration is Ownable, ReentrancyGuard {
// State variables
AgentBounty public agentBounty;
AgentStaking public agentStaking;
PerformanceVerifier public performanceVerifier;
AIToken public aitbcToken;
uint256 public integrationCounter;
uint256 public autoVerificationThreshold = 90; // 90% accuracy for auto-verification
uint256 public batchProcessingLimit = 50;
uint256 public gasOptimizationThreshold = 100000;
// Integration status
enum IntegrationStatus { PENDING, PROCESSING, COMPLETED, FAILED }
// Performance to bounty mapping
struct PerformanceMapping {
uint256 mappingId;
bytes32 performanceHash;
uint256 bountyId;
uint256 submissionId;
IntegrationStatus status;
uint256 createdAt;
uint256 processedAt;
string errorMessage;
}
// Batch processing
struct BatchRequest {
uint256 batchId;
uint256[] bountyIds;
uint256[] submissionIds;
bytes32[] performanceHashes;
uint256[] accuracies;
uint256[] responseTimes;
IntegrationStatus status;
uint256 createdAt;
uint256 processedAt;
uint256 successCount;
uint256 failureCount;
}
// Event handlers
struct EventHandler {
bytes32 eventType;
address targetContract;
bytes4 functionSelector;
bool isActive;
uint256 priority;
}
// Mappings
mapping(uint256 => PerformanceMapping) public performanceMappings;
mapping(bytes32 => uint256) public performanceHashToMapping;
mapping(uint256 => BatchRequest) public batchRequests;
mapping(bytes32 => EventHandler) public eventHandlers;
mapping(address => bool) public authorizedIntegrators;
// Arrays
uint256[] public pendingMappings;
bytes32[] public performanceHashes;
address[] public authorizedIntegratorList;
// Events
event PerformanceMapped(
uint256 indexed mappingId,
bytes32 indexed performanceHash,
uint256 indexed bountyId,
uint256 submissionId
);
event BountyAutoCompleted(
uint256 indexed bountyId,
uint256 indexed submissionId,
address indexed submitter,
uint256 rewardAmount
);
event StakingRewardsTriggered(
address indexed agentWallet,
uint256 totalEarnings,
uint256 stakerCount
);
event BatchProcessed(
uint256 indexed batchId,
uint256 successCount,
uint256 failureCount,
uint256 gasUsed
);
event IntegrationFailed(
uint256 indexed mappingId,
string errorMessage,
bytes32 indexed performanceHash
);
event EventHandlerRegistered(
bytes32 indexed eventType,
address indexed targetContract,
bytes4 functionSelector
);
// Modifiers
modifier mappingExists(uint256 _mappingId) {
require(_mappingId < integrationCounter, "Mapping does not exist");
_;
}
modifier onlyAuthorizedIntegrator() {
require(authorizedIntegrators[msg.sender], "Not authorized integrator");
_;
}
modifier validPerformanceHash(bytes32 _performanceHash) {
require(_performanceHash != bytes32(0), "Invalid performance hash");
_;
}
constructor(
address _agentBounty,
address _agentStaking,
address _performanceVerifier,
address _aitbcToken
) {
agentBounty = AgentBounty(_agentBounty);
agentStaking = AgentStaking(_agentStaking);
performanceVerifier = PerformanceVerifier(_performanceVerifier);
aitbcToken = AIToken(_aitbcToken);
// Register default event handlers
_registerEventHandler(
keccak256("BOUNTY_COMPLETED"),
_agentStaking,
AgentStaking.distributeAgentEarnings.selector
);
_registerEventHandler(
keccak256("PERFORMANCE_VERIFIED"),
_agentBounty,
AgentBounty.verifySubmission.selector
);
}
/**
* @dev Maps performance verification to bounty completion
* @param _performanceHash Hash of performance metrics
* @param _bountyId Bounty ID
* @param _submissionId Submission ID
*/
function mapPerformanceToBounty(
bytes32 _performanceHash,
uint256 _bountyId,
uint256 _submissionId
) external
onlyAuthorizedIntegrator
validPerformanceHash(_performanceHash)
nonReentrant
returns (uint256)
{
require(performanceHashToMapping[_performanceHash] == 0, "Performance already mapped");
uint256 mappingId = integrationCounter++;
PerformanceMapping storage mapping = performanceMappings[mappingId];
mapping.mappingId = mappingId;
mapping.performanceHash = _performanceHash;
mapping.bountyId = _bountyId;
mapping.submissionId = _submissionId;
mapping.status = IntegrationStatus.PENDING;
mapping.createdAt = block.timestamp;
performanceHashToMapping[_performanceHash] = mappingId;
pendingMappings.push(mappingId);
performanceHashes.push(_performanceHash);
emit PerformanceMapped(mappingId, _performanceHash, _bountyId, _submissionId);
// Attempt auto-processing
_processMapping(mappingId);
return mappingId;
}
/**
* @dev Processes a single performance mapping
* @param _mappingId Mapping ID
*/
function processMapping(uint256 _mappingId) external
onlyAuthorizedIntegrator
mappingExists(_mappingId)
nonReentrant
{
_processMapping(_mappingId);
}
/**
* @dev Processes multiple mappings in a batch
* @param _mappingIds Array of mapping IDs
*/
function processBatchMappings(uint256[] calldata _mappingIds) external
onlyAuthorizedIntegrator
nonReentrant
{
require(_mappingIds.length <= batchProcessingLimit, "Batch too large");
uint256 batchId = integrationCounter++;
BatchRequest storage batch = batchRequests[batchId];
batch.batchId = batchId;
batch.bountyIds = new uint256[](_mappingIds.length);
batch.submissionIds = new uint256[](_mappingIds.length);
batch.performanceHashes = new bytes32[](_mappingIds.length);
batch.accuracies = new uint256[](_mappingIds.length);
batch.responseTimes = new uint256[](_mappingIds.length);
batch.status = IntegrationStatus.PROCESSING;
batch.createdAt = block.timestamp;
uint256 gasStart = gasleft();
uint256 successCount = 0;
uint256 failureCount = 0;
for (uint256 i = 0; i < _mappingIds.length; i++) {
try this._processMappingInternal(_mappingIds[i]) {
successCount++;
} catch {
failureCount++;
}
}
batch.successCount = successCount;
batch.failureCount = failureCount;
batch.processedAt = block.timestamp;
batch.status = IntegrationStatus.COMPLETED;
uint256 gasUsed = gasStart - gasleft();
emit BatchProcessed(batchId, successCount, failureCount, gasUsed);
}
/**
* @dev Auto-verifies bounty submissions based on performance metrics
* @param _bountyId Bounty ID
* @param _submissionId Submission ID
* @param _accuracy Achieved accuracy
* @param _responseTime Response time
*/
function autoVerifyBountySubmission(
uint256 _bountyId,
uint256 _submissionId,
uint256 _accuracy,
uint256 _responseTime
) external
onlyAuthorizedIntegrator
nonReentrant
{
// Get bounty details
(,,,,,, bytes32 performanceCriteria, uint256 minAccuracy,,,, bool requiresZKProof) = agentBounty.getBounty(_bountyId);
// Check if auto-verification conditions are met
if (_accuracy >= autoVerificationThreshold && _accuracy >= minAccuracy) {
// Verify the submission
agentBounty.verifySubmission(_bountyId, _submissionId, true, address(this));
// Get submission details to calculate rewards
(address submitter,,,,,,,) = agentBounty.getSubmission(_submissionId);
// Trigger staking rewards if applicable
_triggerStakingRewards(submitter, _accuracy);
emit BountyAutoCompleted(_bountyId, _submissionId, submitter, 0); // Reward amount will be set by bounty contract
}
}
/**
* @dev Handles performance verification events
* @param _verificationId Performance verification ID
* @param _accuracy Accuracy achieved
* @param _responseTime Response time
* @param _performanceHash Hash of performance metrics
*/
function handlePerformanceVerified(
uint256 _verificationId,
uint256 _accuracy,
uint256 _responseTime,
bytes32 _performanceHash
) external
onlyAuthorizedIntegrator
nonReentrant
{
// Check if this performance is mapped to any bounties
uint256 mappingId = performanceHashToMapping[_performanceHash];
if (mappingId > 0) {
PerformanceMapping storage mapping = performanceMappings[mappingId];
// Update agent staking metrics
(address submitter,,,,,,,) = agentBounty.getSubmission(mapping.submissionId);
agentStaking.updateAgentPerformance(submitter, _accuracy, _accuracy >= autoVerificationThreshold);
// Auto-verify bounty if conditions are met
_autoVerifyBounty(mapping.bountyId, mapping.submissionId, _accuracy, _responseTime);
}
}
/**
* @dev Registers an event handler for cross-contract communication
* @param _eventType Event type identifier
* @param _targetContract Target contract address
* @param _functionSelector Function selector to call
*/
function registerEventHandler(
bytes32 _eventType,
address _targetContract,
bytes4 _functionSelector
) external onlyOwner {
require(_targetContract != address(0), "Invalid target contract");
require(_functionSelector != bytes4(0), "Invalid function selector");
eventHandlers[_eventType] = EventHandler({
eventType: _eventType,
targetContract: _targetContract,
functionSelector: _functionSelector,
isActive: true,
priority: 0
});
emit EventHandlerRegistered(_eventType, _targetContract, _functionSelector);
}
/**
* @dev Authorizes an integrator address
* @param _integrator Address to authorize
*/
function authorizeIntegrator(address _integrator) external onlyOwner {
require(_integrator != address(0), "Invalid integrator address");
require(!authorizedIntegrators[_integrator], "Already authorized");
authorizedIntegrators[_integrator] = true;
authorizedIntegratorList.push(_integrator);
}
/**
* @dev Revokes integrator authorization
* @param _integrator Address to revoke
*/
function revokeIntegrator(address _integrator) external onlyOwner {
require(authorizedIntegrators[_integrator], "Not authorized");
authorizedIntegrators[_integrator] = false;
// Remove from list
for (uint256 i = 0; i < authorizedIntegratorList.length; i++) {
if (authorizedIntegratorList[i] == _integrator) {
authorizedIntegratorList[i] = authorizedIntegratorList[authorizedIntegratorList.length - 1];
authorizedIntegratorList.pop();
break;
}
}
}
/**
* @dev Updates configuration parameters
* @param _autoVerificationThreshold New auto-verification threshold
* @param _batchProcessingLimit New batch processing limit
* @param _gasOptimizationThreshold New gas optimization threshold
*/
function updateConfiguration(
uint256 _autoVerificationThreshold,
uint256 _batchProcessingLimit,
uint256 _gasOptimizationThreshold
) external onlyOwner {
require(_autoVerificationThreshold <= 100, "Invalid threshold");
require(_batchProcessingLimit <= 100, "Batch limit too high");
autoVerificationThreshold = _autoVerificationThreshold;
batchProcessingLimit = _batchProcessingLimit;
gasOptimizationThreshold = _gasOptimizationThreshold;
}
// View functions
/**
* @dev Gets performance mapping details
* @param _mappingId Mapping ID
*/
function getPerformanceMapping(uint256 _mappingId) external view mappingExists(_mappingId) returns (
bytes32 performanceHash,
uint256 bountyId,
uint256 submissionId,
IntegrationStatus status,
uint256 createdAt,
uint256 processedAt,
string memory errorMessage
) {
PerformanceMapping storage mapping = performanceMappings[_mappingId];
return (
mapping.performanceHash,
mapping.bountyId,
mapping.submissionId,
mapping.status,
mapping.createdAt,
mapping.processedAt,
mapping.errorMessage
);
}
/**
* @dev Gets batch request details
* @param _batchId Batch ID
*/
function getBatchRequest(uint256 _batchId) external view returns (
uint256[] memory bountyIds,
uint256[] memory submissionIds,
IntegrationStatus status,
uint256 createdAt,
uint256 processedAt,
uint256 successCount,
uint256 failureCount
) {
BatchRequest storage batch = batchRequests[_batchId];
return (
batch.bountyIds,
batch.submissionIds,
batch.status,
batch.createdAt,
batch.processedAt,
batch.successCount,
batch.failureCount
);
}
/**
* @dev Gets pending mappings
*/
function getPendingMappings() external view returns (uint256[] memory) {
return pendingMappings;
}
/**
* @dev Gets all performance hashes
*/
function getPerformanceHashes() external view returns (bytes32[] memory) {
return performanceHashes;
}
/**
* @dev Gets authorized integrators
*/
function getAuthorizedIntegrators() external view returns (address[] memory) {
return authorizedIntegratorList;
}
/**
* @dev Checks if an address is authorized
* @param _integrator Address to check
*/
function isAuthorizedIntegrator(address _integrator) external view returns (bool) {
return authorizedIntegrators[_integrator];
}
/**
* @dev Gets integration statistics
*/
function getIntegrationStats() external view returns (
uint256 totalMappings,
uint256 pendingCount,
uint256 completedCount,
uint256 failedCount,
uint256 averageProcessingTime
) {
uint256 completed = 0;
uint256 failed = 0;
uint256 totalTime = 0;
uint256 processedCount = 0;
for (uint256 i = 0; i < integrationCounter; i++) {
PerformanceMapping storage mapping = performanceMappings[i];
if (mapping.status == IntegrationStatus.COMPLETED) {
completed++;
totalTime += mapping.processedAt - mapping.createdAt;
processedCount++;
} else if (mapping.status == IntegrationStatus.FAILED) {
failed++;
}
}
uint256 avgTime = processedCount > 0 ? totalTime / processedCount : 0;
return (
integrationCounter,
pendingMappings.length,
completed,
failed,
avgTime
);
}
// Internal functions
function _processMapping(uint256 _mappingId) internal {
PerformanceMapping storage mapping = performanceMappings[_mappingId];
if (mapping.status != IntegrationStatus.PENDING) {
return;
}
try this._processMappingInternal(_mappingId) {
mapping.status = IntegrationStatus.COMPLETED;
mapping.processedAt = block.timestamp;
} catch Error(string memory reason) {
mapping.status = IntegrationStatus.FAILED;
mapping.errorMessage = reason;
mapping.processedAt = block.timestamp;
emit IntegrationFailed(_mappingId, reason, mapping.performanceHash);
} catch {
mapping.status = IntegrationStatus.FAILED;
mapping.errorMessage = "Unknown error";
mapping.processedAt = block.timestamp;
emit IntegrationFailed(_mappingId, "Unknown error", mapping.performanceHash);
}
// Remove from pending
_removeFromPending(_mappingId);
}
function _processMappingInternal(uint256 _mappingId) external {
PerformanceMapping storage mapping = performanceMappings[_mappingId];
// Get bounty details
(,,,,,, bytes32 performanceCriteria, uint256 minAccuracy,,,, bool requiresZKProof) = agentBounty.getBounty(mapping.bountyId);
// Get submission details
(address submitter, bytes32 submissionHash, uint256 accuracy, uint256 responseTime,,,) = agentBounty.getSubmission(mapping.submissionId);
// Verify performance criteria match
require(mapping.performanceHash == submissionHash, "Performance hash mismatch");
// Check if accuracy meets requirements
require(accuracy >= minAccuracy, "Accuracy below minimum");
// Auto-verify if conditions are met
if (accuracy >= autoVerificationThreshold) {
agentBounty.verifySubmission(mapping.bountyId, mapping.submissionId, true, address(this));
// Update agent staking metrics
agentStaking.updateAgentPerformance(submitter, accuracy, true);
// Trigger staking rewards
_triggerStakingRewards(submitter, accuracy);
}
}
function _autoVerifyBounty(
uint256 _bountyId,
uint256 _submissionId,
uint256 _accuracy,
uint256 _responseTime
) internal {
if (_accuracy >= autoVerificationThreshold) {
agentBounty.verifySubmission(_bountyId, _submissionId, true, address(this));
}
}
function _triggerStakingRewards(address _agentWallet, uint256 _accuracy) internal {
// Calculate earnings based on accuracy
uint256 baseEarnings = (_accuracy * 100) * 10**18; // Simplified calculation
// Distribute to stakers
try agentStaking.distributeAgentEarnings(_agentWallet, baseEarnings) {
emit StakingRewardsTriggered(_agentWallet, baseEarnings, 0);
} catch {
// Handle staking distribution failure
}
}
function _registerEventHandler(
bytes32 _eventType,
address _targetContract,
bytes4 _functionSelector
) internal {
eventHandlers[_eventType] = EventHandler({
eventType: _eventType,
targetContract: _targetContract,
functionSelector: _functionSelector,
isActive: true,
priority: 0
});
}
function _removeFromPending(uint256 _mappingId) internal {
for (uint256 i = 0; i < pendingMappings.length; i++) {
if (pendingMappings[i] == _mappingId) {
pendingMappings[i] = pendingMappings[pendingMappings.length - 1];
pendingMappings.pop();
break;
}
}
}
}