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.
| Property | Value |
|---|---|
| Max Quality Score | 100 |
| Quality Multiplier Base | 100 (1x) |
| Max Oracles | 10 |
| Default Heartbeat | 1 hour |
| Network | Base (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 multiplierReward Calculation
qualityBonus = (qualityScore * qualityMultiplier) / 100
reward = baseReward * (100 + qualityBonus) / 100Example:
- 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:
| Type | Hash | Description |
|---|---|---|
| Photo | keccak256("PHOTO") | Image contributions |
| Video | keccak256("VIDEO") | Video contributions |
| Voice | keccak256("VOICE") | Audio/voice recordings |
| Survey | keccak256("SURVEY") | Survey responses |
| Location | keccak256("LOCATION") | Location data |
Setting a contribution type (admin):
function setContributionType(
bytes32 typeId,
uint256 baseReward,
uint128 qualityMultiplier,
bool active
) external onlyOwnerFunctions
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| Parameter | Type | Description |
|---|---|---|
contributionId | bytes32 | Unique identifier for the contribution |
contributor | address | User who made the contribution |
contributionType | bytes32 | Type hash (e.g., keccak256("PHOTO")) |
qualityScore | uint8 | Quality score from 0-100 |
Consensus Flow:
- Each oracle calls
confirmContribution()with the same parameters - Contract tracks confirmations per unique parameter hash
- When
minOracleConfirmationsis reached, reward is auto-executed - 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 whenNotPausedUser Functions
claimRewards
Claim all pending rewards.
function claimRewards() external nonReentrant whenNotPausedRequirements:
- 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 nonReentrantView 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| Parameter | Type | Description |
|---|---|---|
typeId | bytes32 | Unique identifier (e.g., keccak256("PHOTO")) |
baseReward | uint256 | Base reward amount in wei |
qualityMultiplier | uint128 | Quality bonus multiplier (100 = 1x) |
active | bool | Whether the type is active |
setMinOracleConfirmations
Set the minimum number of oracle confirmations required.
function setMinOracleConfirmations(uint256 _minConfirmations) external onlyOwneraddOracle / removeOracle
Manage authorized oracles.
function addOracle(address newOracle) external onlyOwner
function removeOracle(address oracleToRemove) external onlyOwnerwithdrawRewards
Withdraw rewards from the pool (owner only).
function withdrawRewards(uint256 amount) external onlyOwnerEvents
/// @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-executedUser: 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
-
Multi-Oracle Consensus: A single compromised oracle cannot award fraudulent rewards. Multiple oracles must independently verify the same contribution details before rewards are distributed.
-
Unique Contribution IDs: Each contribution ID can only be rewarded once. The system tracks processed contributions to prevent double-claiming.
-
Oracle Heartbeat: Oracles must maintain fresh heartbeats (default: 1 hour). Stale oracles cannot confirm contributions, preventing attacks via dormant compromised oracles.
-
Quality Score Validation: Quality scores are capped at 100 to prevent overflow-based attacks.
-
Rewards Pool Management: The contract tracks the rewards pool separately from staked tokens. Users cannot claim more than what's in the pool.
-
Contribution Type Validation: Only active contribution types with non-zero base rewards can receive rewards.