"""Proof-of-contribution labor issuance channel. Non-financial minting: verified labor/contribution units are converted to $MYCO at a governed rate, subject to rate limits (flow dampening). Design: - An attestation system (oracle, DAO vote, or peer review) reports "contribution units" for a contributor - Each unit converts to $MYCO at a governed rate (tokens_per_unit) - Rate limits prevent gaming: max units per period, cooldown between claims - Contribution types have different conversion rates (code, governance, community) This is the "subscription-based bonding curve" concept from the MycoFi paper, extended to general labor contributions. """ import numpy as np from dataclasses import dataclass, field from typing import Optional @dataclass class ContributionType: """A type of contribution with its conversion rate.""" name: str tokens_per_unit: float # $MYCO per contribution unit max_units_per_period: float # Rate limit per period period_length: float # Length of rate-limit period (time units) cooldown: float = 0.0 # Minimum time between claims decay_rate: float = 0.0 # Annual decay of unclaimed units (use-it-or-lose-it) @dataclass class ContributorState: """State for a single contributor.""" address: str unclaimed_units: dict[str, float] = field(default_factory=dict) claimed_this_period: dict[str, float] = field(default_factory=dict) period_start: dict[str, float] = field(default_factory=dict) last_claim_time: dict[str, float] = field(default_factory=dict) total_claimed: float = 0.0 @dataclass class LaborIssuanceSystem: """The labor issuance channel.""" contribution_types: dict[str, ContributionType] global_rate_multiplier: float = 1.0 # Governance can scale all rates total_minted: float = 0.0 max_total_mint: float = float("inf") # Cap on total labor-minted tokens def attest_contribution( system: LaborIssuanceSystem, contributor: ContributorState, contribution_type: str, units: float, current_time: float, ) -> ContributorState: """Record a contribution attestation. Called by the oracle/attestation layer when a contribution is verified. Does NOT mint tokens — just records units for later claiming. """ assert contribution_type in system.contribution_types assert units > 0 ct = system.contribution_types[contribution_type] # Apply decay to existing unclaimed units if contribution_type in contributor.unclaimed_units: last_time = contributor.last_claim_time.get(contribution_type, current_time) elapsed = current_time - last_time if ct.decay_rate > 0 and elapsed > 0: decay = np.exp(-ct.decay_rate * elapsed) contributor.unclaimed_units[contribution_type] *= decay # Add new units current = contributor.unclaimed_units.get(contribution_type, 0.0) contributor.unclaimed_units[contribution_type] = current + units return contributor def claim_tokens( system: LaborIssuanceSystem, contributor: ContributorState, contribution_type: str, current_time: float, units_to_claim: float | None = None, ) -> tuple[LaborIssuanceSystem, ContributorState, float]: """Claim $MYCO for verified contributions. Returns (updated_system, updated_contributor, tokens_minted). """ assert contribution_type in system.contribution_types ct = system.contribution_types[contribution_type] # Check cooldown last_claim = contributor.last_claim_time.get(contribution_type, -float("inf")) if current_time - last_claim < ct.cooldown: return system, contributor, 0.0 # Available units available = contributor.unclaimed_units.get(contribution_type, 0.0) if available <= 0: return system, contributor, 0.0 # Check rate limit (reset period if needed) period_start = contributor.period_start.get(contribution_type, current_time) if current_time - period_start >= ct.period_length: # New period contributor.claimed_this_period[contribution_type] = 0.0 contributor.period_start[contribution_type] = current_time claimed_so_far = contributor.claimed_this_period.get(contribution_type, 0.0) remaining_allowance = ct.max_units_per_period - claimed_so_far # Determine how many units to claim if units_to_claim is not None: to_claim = min(units_to_claim, available, remaining_allowance) else: to_claim = min(available, remaining_allowance) if to_claim <= 0: return system, contributor, 0.0 # Compute tokens tokens = to_claim * ct.tokens_per_unit * system.global_rate_multiplier # Check global cap tokens = min(tokens, system.max_total_mint - system.total_minted) if tokens <= 0: return system, contributor, 0.0 # Update state contributor.unclaimed_units[contribution_type] -= to_claim contributor.claimed_this_period[contribution_type] = claimed_so_far + to_claim contributor.last_claim_time[contribution_type] = current_time contributor.total_claimed += tokens system.total_minted += tokens return system, contributor, tokens def create_default_system() -> LaborIssuanceSystem: """Create a default labor issuance system with common contribution types.""" types = { "code": ContributionType( name="Code Contribution", tokens_per_unit=10.0, # 10 MYCO per merged PR equivalent max_units_per_period=50.0, # Max 50 units per period period_length=30.0, # 30-day periods cooldown=1.0, # 1-day cooldown between claims ), "governance": ContributionType( name="Governance Participation", tokens_per_unit=5.0, max_units_per_period=20.0, period_length=30.0, cooldown=7.0, # Weekly claims ), "community": ContributionType( name="Community Building", tokens_per_unit=3.0, max_units_per_period=100.0, period_length=30.0, cooldown=1.0, decay_rate=0.1, # Unclaimed units decay ), } return LaborIssuanceSystem(contribution_types=types)