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.
| Property | Value |
|---|---|
| Min Stake | 10 PRIV |
| Expert Stake | 100 PRIV |
| Gold Standard Bonus | 50% |
| Consensus Bonus | 25% |
| Max Slash | 50% |
| Slash Dispute Period | 24 hours |
| Network | Base (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) cancelledContract 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| Parameter | Type | Description |
|---|---|---|
amount | uint256 | Amount 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| Parameter | Type | Description |
|---|---|---|
amount | uint256 | Amount 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| Parameter | Type | Description |
|---|---|---|
taskId | bytes32 | Unique task identifier |
rewardPerSubmission | uint256 | Reward per completion |
validationReward | uint256 | Reward for validators |
totalBudget | uint256 | Total 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) externalwithdrawTaskBudget
Withdraw remaining budget from deactivated task.
function withdrawTaskBudget(bytes32 taskId) external nonReentrantMulti-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| Parameter | Type | Description |
|---|---|---|
taskId | bytes32 | Task ID |
labeler | address | User who completed the task |
rewardAmount | uint256 | Reward amount |
passedGoldStandard | bool | Whether 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| Parameter | Type | Description |
|---|---|---|
taskId | bytes32 | Task ID |
validator | address | Validator address |
consensusMatched | bool | Whether 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)| Parameter | Type | Description |
|---|---|---|
user | address | User to slash |
percentage | uint256 | Slash percentage (1-50) |
reason | string | Reason 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 notStaleOracledisputeSlash
Dispute a slash proposal (only callable by target user).
function disputeSlash(bytes32 proposalId) externalRequirements:
- 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 onlyOracleRequirements:
- 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 whenNotPausedView 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
-
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.
-
24-Hour Dispute Period: Slashed users have 24 hours to dispute. This provides protection against malicious or erroneous slash proposals.
-
Multi-Oracle Slash Consensus: Slash proposals require
minOracleConfirmationsoracle confirmations before execution, preventing single-oracle abuse. -
Max Slash Cap: Maximum slash is 50% per proposal, protecting users from complete stake loss in a single incident.
-
Slash Cooldown: Users cannot be slashed twice within the cooldown period (default 24 hours), preventing excessive slashing.
-
Minimum Stake Requirement: The 10 PRIV minimum stake ensures labelers have skin in the game, reducing spam and low-quality submissions.