PRIV ProtocolPRIV Docs
Contracts

LabelingRewards

AI training labeling task marketplace with stake-based reputation and dispute mechanism.

Overview

LabelingRewards manages a marketplace for AI training labeling tasks. Task requesters post labeling jobs with budgets, and labelers stake PRIV tokens to participate. The contract features multi-oracle consensus for rewards, a gold standard bonus system, and a slash mechanism with a dispute period for quality assurance.

PropertyValue
Min Stake10 PRIV
Expert Stake100 PRIV
Gold Standard Bonus50%
Consensus Bonus25%
Max Slash50%
Slash Dispute Period24 hours
NetworkBase (Chain ID: 8453)

Key Features

  • Stake-Based Participation: Labelers must stake minimum 10 PRIV to participate
  • Expert Tier: 100+ PRIV stake unlocks expert-level tasks
  • Gold Standard Bonus: 50% bonus for passing gold standard validation
  • Consensus Bonus: 25% bonus for validators matching consensus
  • Multi-Oracle Consensus: M-of-N confirmation for task rewards
  • Slash with Dispute: 24-hour dispute period before stake slashing
  • Stake Freeze: Cannot unstake while slash proposals are pending

How It Works

Requester creates task  -->  Labeler stakes PRIV  -->  Completes task
        |                           |                        |
        v                           v                        v
  Budget deposited            Minimum 10 PRIV         Oracles verify
                                                            |
                                                            v
                                                    Rewards distributed
                                                    (+ gold standard bonus)

Slash Flow with Dispute Period

Oracle proposes slash  -->  24hr dispute period  -->  Execute or Dispute
        |                          |                         |
        v                          v                         v
  User's stake frozen        User can dispute         Slash executed or
  (cannot unstake)           (cancels slash)          cancelled

Contract Interface

Constants

/// @notice Minimum stake to participate (10 PRIV)
uint256 public constant MIN_STAKE = 10 * 10 ** 18;

/// @notice Expert-level stake (100 PRIV)
uint256 public constant EXPERT_STAKE = 100 * 10 ** 18;

/// @notice Gold standard bonus (50%)
uint256 public constant GOLD_STANDARD_BONUS = 50;

/// @notice Consensus bonus for validators (25%)
uint256 public constant CONSENSUS_BONUS = 25;

/// @notice Maximum slash percentage (50%)
uint256 public constant MAX_SLASH_PERCENTAGE = 50;

/// @notice Slash dispute period (24 hours)
uint256 public constant SLASH_DISPUTE_PERIOD = 24 hours;

Structs

Task

struct Task {
    address requester;            // Task creator
    uint256 rewardPerSubmission;  // Reward per task completion
    uint256 validationReward;     // Reward for validators
    uint256 totalBudget;          // Total budget allocated
    uint256 remainingBudget;      // Budget remaining
    bool active;                  // Whether task is active
}

UserInfo

struct UserInfo {
    uint256 stakedAmount;      // Amount staked
    uint256 pendingEarnings;   // Earnings to claim
    uint256 totalEarnings;     // Lifetime earnings
    uint256 completedTasks;    // Tasks completed
    uint256 validatedTasks;    // Validations done
    uint256 slashedAmount;     // Total slashed
    uint256 lastActivityAt;    // Last activity timestamp
}

SlashProposal

struct SlashProposal {
    address user;           // User to slash
    uint256 percentage;     // Slash percentage (0-50)
    string reason;          // Reason for slash
    uint256 proposedAt;     // Proposal timestamp
    uint256 confirmations;  // Oracle confirmations
    bool executed;          // Whether executed
    bool disputed;          // Whether disputed
}

Functions

Staking Functions

stake

Stake PRIV tokens to participate in labeling.

function stake(uint256 amount) external nonReentrant whenNotPaused
ParameterTypeDescription
amountuint256Amount of PRIV to stake

Effects:

  • Transfers PRIV from user to contract
  • Updates user's staked amount
  • Updates last activity timestamp

unstake

Unstake PRIV tokens.

function unstake(uint256 amount) external nonReentrant whenNotPaused
ParameterTypeDescription
amountuint256Amount to unstake

Requirements:

  • Amount must not exceed staked amount
  • User must not have pending slash proposals (stake is frozen)

Task Management

createTask

Create a new labeling task.

function createTask(
    bytes32 taskId,
    uint256 rewardPerSubmission,
    uint256 validationReward,
    uint256 totalBudget
) external nonReentrant whenNotPaused
ParameterTypeDescription
taskIdbytes32Unique task identifier
rewardPerSubmissionuint256Reward per completion
validationRewarduint256Reward for validators
totalBudgetuint256Total budget for task

Requirements:

  • Task ID must not already exist
  • Reward per submission must be > 0
  • Total budget must be > 0
  • Caller must approve PRIV transfer

deactivateTask

Deactivate a task (owner or task requester).

function deactivateTask(bytes32 taskId) external

withdrawTaskBudget

Withdraw remaining budget from deactivated task.

function withdrawTaskBudget(bytes32 taskId) external nonReentrant

Multi-Oracle Rewards

confirmTaskReward

Confirm a task reward (M-of-N consensus).

function confirmTaskReward(
    bytes32 taskId,
    address labeler,
    uint256 rewardAmount,
    bool passedGoldStandard
) external onlyOracle notStaleOracle whenNotPaused
ParameterTypeDescription
taskIdbytes32Task ID
labeleraddressUser who completed the task
rewardAmountuint256Reward amount
passedGoldStandardboolWhether gold standard was passed

Requirements:

  • Labeler must have staked >= MIN_STAKE (10 PRIV)
  • Task must exist and be active
  • Labeler must not have already completed this task

Consensus: When minOracleConfirmations oracles confirm, reward is auto-executed with:

  • Base reward + 50% gold standard bonus (if passed)

rewardValidation

Reward a validator for their validation work.

function rewardValidation(
    bytes32 taskId,
    address validator,
    bool consensusMatched
) external onlyOracle notStaleOracle whenNotPaused
ParameterTypeDescription
taskIdbytes32Task ID
validatoraddressValidator address
consensusMatchedboolWhether validation matched consensus

Reward:

  • Base validation reward + 25% consensus bonus (if matched)

Slash System

proposeSlash

Propose a slash against a user (starts 24hr dispute period).

function proposeSlash(
    address user,
    uint256 percentage,
    string calldata reason
) external onlyOracle notStaleOracle returns (bytes32 proposalId)
ParameterTypeDescription
useraddressUser to slash
percentageuint256Slash percentage (1-50)
reasonstringReason for slashing

Effects:

  • Creates slash proposal
  • Freezes user's stake (cannot unstake)
  • Starts 24-hour dispute period

confirmSlash

Confirm a slash proposal (additional oracle support).

function confirmSlash(bytes32 proposalId) external onlyOracle notStaleOracle

disputeSlash

Dispute a slash proposal (only callable by target user).

function disputeSlash(bytes32 proposalId) external

Requirements:

  • Caller must be the slash target
  • Must be called within 24-hour dispute period
  • Proposal must not already be executed or disputed

Effects:

  • Marks proposal as disputed
  • Unfreezes user's stake

executeSlash

Execute a slash proposal after dispute period.

function executeSlash(bytes32 proposalId) external onlyOracle

Requirements:

  • 24-hour dispute period must have elapsed
  • Proposal must not be disputed
  • Must have >= minOracleConfirmations

Effects:

  • Slashes user's stake by the percentage
  • Updates slashed amount tracking
  • Unfreezes user's stake

User Functions

claimEarnings

Claim pending earnings.

function claimEarnings() external nonReentrant whenNotPaused

View Functions

getUserInfo

Get full user info.

function getUserInfo(address user) external view returns (UserInfo memory)

getTask

Get task details.

function getTask(bytes32 taskId) external view returns (Task memory)

isExpertLevel

Check if user meets expert stake threshold.

function isExpertLevel(address user) external view returns (bool)

Returns true if stakedAmount >= EXPERT_STAKE (100 PRIV)


meetsMinimumStake

Check if user meets minimum stake requirement.

function meetsMinimumStake(address user) external view returns (bool)

Returns true if stakedAmount >= MIN_STAKE (10 PRIV)


getSlashProposal

Get slash proposal details.

function getSlashProposal(bytes32 proposalId) external view returns (SlashProposal memory)

canExecuteSlash

Check if a slash proposal can be executed.

function canExecuteSlash(bytes32 proposalId) external view returns (bool)

slashDisputePeriodRemaining

Get time remaining in dispute period.

function slashDisputePeriodRemaining(bytes32 proposalId) external view returns (uint256)

Events

/// @notice Emitted when a task is created
event TaskCreated(bytes32 indexed taskId, address indexed requester, uint256 budget);

/// @notice Emitted when a task is completed
event TaskCompleted(bytes32 indexed taskId, address indexed user, uint256 reward);

/// @notice Emitted when a validation is completed
event ValidationCompleted(bytes32 indexed taskId, address indexed validator, uint256 reward);

/// @notice Emitted when user stakes
event Staked(address indexed user, uint256 amount);

/// @notice Emitted when user unstakes
event Unstaked(address indexed user, uint256 amount);

/// @notice Emitted when stake is slashed
event StakeSlashed(address indexed user, uint256 amount, string reason);

/// @notice Emitted when earnings are claimed
event EarningsClaimed(address indexed user, uint256 amount);

/// @notice Emitted when oracle confirms a reward
event RewardConfirmed(
    bytes32 indexed confirmHash,
    bytes32 indexed taskId,
    address indexed labeler,
    address oracle,
    uint256 rewardAmount,
    uint256 confirmationCount
);

/// @notice Emitted when a slash is proposed
event SlashProposed(
    bytes32 indexed proposalId,
    address indexed user,
    uint256 percentage,
    string reason,
    address proposer
);

/// @notice Emitted when a slash is disputed
event SlashDisputed(bytes32 indexed proposalId, address indexed user);

/// @notice Emitted when a slash is executed
event SlashExecuted(bytes32 indexed proposalId, address indexed user, uint256 amount);

Errors

error InvalidAddress();
error InvalidAmount();
error InvalidPercentage();
error TaskNotFound();
error TaskNotActive();
error TaskAlreadyExists();
error TaskAlreadyCompleted();
error TaskAlreadyValidated();
error InsufficientBudget();
error InsufficientStake();
error NothingToClaim();
error OnlyOracle();
error UnstakeAmountTooHigh();
error UnstakeWhileSlashPending();
error SlashProposalNotFound();
error SlashAlreadyExecuted();
error SlashWasDisputed();
error DisputePeriodNotEnded();
error DisputePeriodEnded();
error NotSlashTarget();
error InsufficientConfirmations();

Usage Examples

Stake to Participate

import { parseEther } from 'viem'

async function stakeForLabeling(amount: string) {
  const amountWei = parseEther(amount)

  // Approve first
  await writeContract({
    address: PRIV_TOKEN_ADDRESS,
    abi: privTokenAbi,
    functionName: 'approve',
    args: [LABELING_REWARDS_ADDRESS, amountWei],
  })

  // Stake
  await writeContract({
    address: LABELING_REWARDS_ADDRESS,
    abi: labelingRewardsAbi,
    functionName: 'stake',
    args: [amountWei],
  })
}

// Stake 10 PRIV minimum to start labeling
await stakeForLabeling('10')

// Stake 100 PRIV for expert level
await stakeForLabeling('100')

Create a Labeling Task

import { keccak256, toHex, parseEther } from 'viem'

async function createTask(
  name: string,
  rewardPerSubmission: string,
  validationReward: string,
  totalBudget: string
) {
  const taskId = keccak256(toHex(name + Date.now()))
  const budget = parseEther(totalBudget)

  // Approve budget
  await writeContract({
    address: PRIV_TOKEN_ADDRESS,
    abi: privTokenAbi,
    functionName: 'approve',
    args: [LABELING_REWARDS_ADDRESS, budget],
  })

  // Create task
  await writeContract({
    address: LABELING_REWARDS_ADDRESS,
    abi: labelingRewardsAbi,
    functionName: 'createTask',
    args: [
      taskId,
      parseEther(rewardPerSubmission),
      parseEther(validationReward),
      budget,
    ],
  })

  return taskId
}

Dispute a Slash Proposal

// If you receive a slash proposal, you have 24 hours to dispute
async function disputeSlash(proposalId: `0x${string}`) {
  await writeContract({
    address: LABELING_REWARDS_ADDRESS,
    abi: labelingRewardsAbi,
    functionName: 'disputeSlash',
    args: [proposalId],
  })
}

Check User Status

function useLabelerStatus(address: string) {
  const { data: userInfo } = useReadContract({
    address: LABELING_REWARDS_ADDRESS,
    abi: labelingRewardsAbi,
    functionName: 'getUserInfo',
    args: [address],
  })

  const { data: isExpert } = useReadContract({
    address: LABELING_REWARDS_ADDRESS,
    abi: labelingRewardsAbi,
    functionName: 'isExpertLevel',
    args: [address],
  })

  const { data: meetsMin } = useReadContract({
    address: LABELING_REWARDS_ADDRESS,
    abi: labelingRewardsAbi,
    functionName: 'meetsMinimumStake',
    args: [address],
  })

  return { userInfo, isExpert, meetsMin }
}

Security Notes

  1. Stake Freeze on Slash Proposal: When a slash is proposed against a user, their stake is frozen. They cannot unstake until the proposal is either executed or disputed. This prevents front-running slash execution.

  2. 24-Hour Dispute Period: Slashed users have 24 hours to dispute. This provides protection against malicious or erroneous slash proposals.

  3. Multi-Oracle Slash Consensus: Slash proposals require minOracleConfirmations oracle confirmations before execution, preventing single-oracle abuse.

  4. Max Slash Cap: Maximum slash is 50% per proposal, protecting users from complete stake loss in a single incident.

  5. Slash Cooldown: Users cannot be slashed twice within the cooldown period (default 24 hours), preventing excessive slashing.

  6. Minimum Stake Requirement: The 10 PRIV minimum stake ensures labelers have skin in the game, reducing spam and low-quality submissions.


Source Code

View on GitHub