PRIV ProtocolPRIV Docs
Contracts

DataContribution

Reward users for data contributions (photos, videos, voice) with multi-oracle consensus.

Overview

DataContribution enables the PRIV Protocol to reward users for contributing valuable data assets like photos, videos, and voice recordings for AI training. The contract uses multi-oracle consensus to verify contributions and prevent fraudulent claims.

PropertyValue
Max Quality Score100
Quality Multiplier Base100 (1x)
Max Oracles10
Default Heartbeat1 hour
NetworkBase (Chain ID: 8453)

Key Features

  • Configurable Contribution Types: Define different types with unique base rewards
  • Quality-Based Rewards: Rewards scale with quality score (0-100)
  • Multi-Oracle Consensus: M-of-N oracle confirmations prevent single-oracle fraud
  • Prevention of Double Claims: Contribution IDs can only be rewarded once
  • Permissionless Pool Funding: Anyone can fund the rewards pool

How It Works

User uploads data  -->  Oracles verify  -->  Consensus reached  -->  User claims
       |                     |                      |                    |
       v                     v                      v                    v
   Contribution ID      Quality assessed      Reward calculated     PRIV transferred
   generated            (0-100 score)         with multiplier

Reward Calculation

qualityBonus = (qualityScore * qualityMultiplier) / 100
reward = baseReward * (100 + qualityBonus) / 100

Example:

  • Base reward: 10 PRIV
  • Quality multiplier: 50 (0.5x bonus potential)
  • Quality score: 80/100
  • Bonus: (80 * 50) / 100 = 40%
  • Final reward: 10 * (100 + 40) / 100 = 14 PRIV

Contract Interface

Constants

/// @notice Maximum quality score (100%)
uint256 public constant MAX_QUALITY_SCORE = 100;

/// @notice Quality multiplier denominator (100 = 1x)
uint256 public constant MULTIPLIER_DENOMINATOR = 100;

/// @notice Precision for calculations
uint256 public constant PRECISION = 1e18;

/// @notice Maximum number of oracles
uint256 public constant MAX_ORACLES = 10;

Structs

ContributionType

struct ContributionType {
    uint256 baseReward;        // Base PRIV reward in wei
    uint128 qualityMultiplier; // Quality bonus multiplier (100 = 1x, 200 = 2x)
    bool active;               // Whether type is active
}

Contribution Types

Contribution types are identified by bytes32 hashes. Common types:

TypeHashDescription
Photokeccak256("PHOTO")Image contributions
Videokeccak256("VIDEO")Video contributions
Voicekeccak256("VOICE")Audio/voice recordings
Surveykeccak256("SURVEY")Survey responses
Locationkeccak256("LOCATION")Location data

Setting a contribution type (admin):

function setContributionType(
    bytes32 typeId,
    uint256 baseReward,
    uint128 qualityMultiplier,
    bool active
) external onlyOwner

Functions

Multi-Oracle Consensus

confirmContribution

Confirm a contribution (called by each oracle).

function confirmContribution(
    bytes32 contributionId,
    address contributor,
    bytes32 contributionType,
    uint8 qualityScore
) external onlyOracle notStaleOracle whenNotPaused
ParameterTypeDescription
contributionIdbytes32Unique identifier for the contribution
contributoraddressUser who made the contribution
contributionTypebytes32Type hash (e.g., keccak256("PHOTO"))
qualityScoreuint8Quality score from 0-100

Consensus Flow:

  1. Each oracle calls confirmContribution() with the same parameters
  2. Contract tracks confirmations per unique parameter hash
  3. When minOracleConfirmations is reached, reward is auto-executed
  4. Contributor's pending rewards are credited

Confirmation Hash:

confirmationHash = keccak256(abi.encodePacked(
    contributionId,
    contributor,
    contributionType,
    qualityScore
))

Requirements:

  • Oracle must be authorized and have fresh heartbeat
  • Contribution must not already be processed
  • Contribution type must exist and be active
  • Quality score must be 100 or less

Legacy Function

rewardContribution

Legacy single-oracle function (routes through consensus when minOracleConfirmations > 1).

function rewardContribution(
    address user,
    bytes32 contributionId,
    bytes32 contributionType,
    uint256 qualityScore
) external onlyOracle notStaleOracle whenNotPaused

User Functions

claimRewards

Claim all pending rewards.

function claimRewards() external nonReentrant whenNotPaused

Requirements:

  • User must have pending rewards > 0
  • Rewards pool must have sufficient balance
  • Contract must not be paused

fundRewardsPool

Add tokens to the rewards pool. Anyone can call this.

function fundRewardsPool(uint256 amount) external nonReentrant

View Functions

getContributionType

Get configuration for a contribution type.

function getContributionType(bytes32 typeId) external view returns (ContributionType memory)

isContributionProcessed

Check if a contribution has already been rewarded.

function isContributionProcessed(bytes32 contributionId) external view returns (bool)

calculateReward

Preview the reward for a contribution.

function calculateReward(
    bytes32 contributionType,
    uint256 qualityScore
) external view returns (uint256)

getUserStats

Get user statistics.

function getUserStats(address user) external view returns (
    uint256 pending,
    uint256 total,
    uint256 count
)

getConfirmationHash

Compute the confirmation hash for a contribution.

function getConfirmationHash(
    bytes32 contributionId,
    address contributor,
    bytes32 contributionType,
    uint8 qualityScore
) external pure returns (bytes32)

hasOracleConfirmed

Check if an oracle has confirmed a specific contribution.

function hasOracleConfirmed(bytes32 confirmationHash, address oracleAddress) external view returns (bool)

Admin Functions

setContributionType

Create or update a contribution type.

function setContributionType(
    bytes32 typeId,
    uint256 baseReward,
    uint128 qualityMultiplier,
    bool active
) external onlyOwner
ParameterTypeDescription
typeIdbytes32Unique identifier (e.g., keccak256("PHOTO"))
baseRewarduint256Base reward amount in wei
qualityMultiplieruint128Quality bonus multiplier (100 = 1x)
activeboolWhether the type is active

setMinOracleConfirmations

Set the minimum number of oracle confirmations required.

function setMinOracleConfirmations(uint256 _minConfirmations) external onlyOwner

addOracle / removeOracle

Manage authorized oracles.

function addOracle(address newOracle) external onlyOwner
function removeOracle(address oracleToRemove) external onlyOwner

withdrawRewards

Withdraw rewards from the pool (owner only).

function withdrawRewards(uint256 amount) external onlyOwner

Events

/// @notice Emitted when a contribution is rewarded
event ContributionRewarded(
    address indexed user,
    bytes32 indexed contributionId,
    bytes32 indexed contributionType,
    uint256 reward
);

/// @notice Emitted when a user claims rewards
event RewardsClaimed(address indexed user, uint256 amount);

/// @notice Emitted when a contribution type is updated
event ContributionTypeUpdated(
    bytes32 indexed typeId,
    uint256 baseReward,
    uint128 qualityMultiplier,
    bool active
);

/// @notice Emitted when an oracle confirms a contribution
event ContributionConfirmed(
    address indexed oracle,
    bytes32 indexed confirmationHash,
    bytes32 indexed contributionId,
    address contributor,
    uint256 currentConfirmations
);

/// @notice Emitted when the rewards pool is funded
event RewardsPoolFunded(address indexed funder, uint256 amount);

/// @notice Emitted when an oracle is added
event OracleAdded(address indexed oracle);

/// @notice Emitted when an oracle is removed
event OracleRemoved(address indexed oracle);

/// @notice Emitted when minOracleConfirmations is updated
event MinOracleConfirmationsUpdated(uint256 oldMin, uint256 newMin);

Errors

error InvalidAddress();
error InvalidAmount();
error InvalidQualityScore();
error ContributionAlreadyProcessed();
error ContributionTypeNotActive();
error ContributionTypeNotFound();
error NothingToClaim();
error InsufficientRewardsPool();
error OnlyOracle();
error OracleAlreadyExists();
error OracleNotFound();
error OracleDataStale();
error AlreadyConfirmed();
error ConfirmationAlreadyExecuted();

Usage Examples

Admin: Set Up Contribution Types

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

// Photo contributions: 5 PRIV base, 50% quality bonus potential
await writeContract({
  address: DATA_CONTRIBUTION_ADDRESS,
  abi: dataContributionAbi,
  functionName: 'setContributionType',
  args: [
    keccak256(toHex('PHOTO')),
    parseEther('5'),    // 5 PRIV base
    50n,                // 50% max quality bonus
    true                // active
  ],
})

// Video contributions: 20 PRIV base, 100% quality bonus potential
await writeContract({
  address: DATA_CONTRIBUTION_ADDRESS,
  abi: dataContributionAbi,
  functionName: 'setContributionType',
  args: [
    keccak256(toHex('VIDEO')),
    parseEther('20'),   // 20 PRIV base
    100n,               // 100% max quality bonus
    true                // active
  ],
})

Oracle: Confirm a Contribution

// Called by authorized oracles
async function confirmContribution(
  contributionId: `0x${string}`,
  contributor: string,
  type: string,
  qualityScore: number
) {
  const typeHash = keccak256(toHex(type))

  await writeContract({
    address: DATA_CONTRIBUTION_ADDRESS,
    abi: dataContributionAbi,
    functionName: 'confirmContribution',
    args: [contributionId, contributor, typeHash, qualityScore],
  })
}

// Example: Two oracles confirm the same contribution
await confirmContribution('0xabc...', '0xuser...', 'PHOTO', 85)
// When second oracle confirms, reward is auto-executed

User: Claim Rewards

async function claimRewards() {
  await writeContract({
    address: DATA_CONTRIBUTION_ADDRESS,
    abi: dataContributionAbi,
    functionName: 'claimRewards',
  })
}

Check User Stats

function useUserStats(address: string) {
  return useReadContract({
    address: DATA_CONTRIBUTION_ADDRESS,
    abi: dataContributionAbi,
    functionName: 'getUserStats',
    args: [address],
  })
}

// Returns: { pending, total, count }

Preview Reward

function usePreviewReward(type: string, qualityScore: number) {
  const typeHash = keccak256(toHex(type))

  return useReadContract({
    address: DATA_CONTRIBUTION_ADDRESS,
    abi: dataContributionAbi,
    functionName: 'calculateReward',
    args: [typeHash, BigInt(qualityScore)],
  })
}

Security Notes

  1. Multi-Oracle Consensus: A single compromised oracle cannot award fraudulent rewards. Multiple oracles must independently verify the same contribution details before rewards are distributed.

  2. Unique Contribution IDs: Each contribution ID can only be rewarded once. The system tracks processed contributions to prevent double-claiming.

  3. Oracle Heartbeat: Oracles must maintain fresh heartbeats (default: 1 hour). Stale oracles cannot confirm contributions, preventing attacks via dormant compromised oracles.

  4. Quality Score Validation: Quality scores are capped at 100 to prevent overflow-based attacks.

  5. Rewards Pool Management: The contract tracks the rewards pool separately from staked tokens. Users cannot claim more than what's in the pool.

  6. Contribution Type Validation: Only active contribution types with non-zero base rewards can receive rewards.


Source Code

View on GitHub