ReputationAggregator Contract¶
The ReputationAggregator is the most sophisticated contract in the Verdikta ecosystem, implementing a secure commit-reveal mechanism for multi-oracle AI evaluations. It provides maximum security and decentralization for high-stakes dispute resolution.
Overview¶
The ReputationAggregator uses a two-phase polling system:
- Commit Phase: K oracles submit encrypted commitments
- Reveal Phase: First M oracles that committed are asked to reveal their actual responses
- Aggregation: First N valid reveals are clustered and aggregated
- Bonus Distribution: Oracles in the best cluster receive bonus payments
Default Configuration¶
- K = 5 (oracles polled in commit phase)
- M = 4 (commits promoted to reveal)
- N = 3 (reveals required for aggregation)
- P = 2 (cluster size for bonus)
- B = 3 (bonus multiplier)
Key Features¶
- Commit-Reveal Security: Prevents oracle manipulation and front-running
- Reputation-Based Selection: Oracles chosen based on quality and timeliness scores
- Clustering Algorithm: Groups similar responses to identify consensus
- Bonus System: Rewards oracles that provide consensus responses
- Timeout Handling: Automatic finalization when oracles fail to respond
Contract Interface¶
Primary Function¶
function requestAIEvaluationWithApproval(
string[] memory cids,
string memory addendumText,
uint256 _alpha,
uint256 _maxOracleFee,
uint256 _estimatedBaseCost,
uint256 _maxFeeBasedScalingFactor,
uint64 _requestedClass
) external returns (bytes32 aggRequestId)
Parameters¶
Parameter | Type | Description |
---|---|---|
cids | string[] | IPFS content hashes containing evidence |
addendumText | string | Additional context (max 1000 chars) |
_alpha | uint256 | Reputation weight (0-1000), higher values favor timeliness |
_maxOracleFee | uint256 | Maximum fee willing to pay per oracle (in LINK) |
_estimatedBaseCost | uint256 | Estimated base cost for fee calculations |
_maxFeeBasedScalingFactor | uint256 | Maximum scaling factor for fee weighting |
_requestedClass | uint64 | Oracle class/type identifier |
Returns¶
aggRequestId
: Unique identifier for tracking the aggregation request
View Functions¶
// Get aggregation result
function getEvaluation(bytes32 aggId)
external view returns (
uint256[] memory likelihoods,
string memory justificationCID,
bool exists
)
// Check maximum total fee required
function maxTotalFee(uint256 requestedMaxOracleFee)
external view returns (uint256)
// Get timeout duration
function responseTimeoutSeconds() external view returns (uint256)
Configuration Functions (Owner Only)¶
// Set phase parameters (K, M, N, P)
function setPhaseCounts(uint256 _k, uint256 _m, uint256 _n, uint256 _p) external onlyOwner
// Set response timeout
function setResponseTimeout(uint256 _seconds) external onlyOwner
// Set reputation weight range
function setAlpha(uint256 _alpha) external onlyOwner
// Set maximum oracle fee
function setMaxOracleFee(uint256 _newMax) external onlyOwner
Events¶
// Request initiated
event RequestAIEvaluation(bytes32 indexed aggRequestId, string[] cids);
// Commit received from oracle
event CommitReceived(bytes32 indexed aggRequestId, uint256 pollIndex, address operator, bytes16 commitHash);
// Commit phase completed, moving to reveal
event CommitPhaseComplete(bytes32 indexed aggRequestId);
// Reveal request sent to oracle
event RevealRequestDispatched(bytes32 indexed aggRequestId, uint256 pollIndex, bytes16 commitHash);
// Oracle response recorded
event NewOracleResponseRecorded(bytes32 requestId, uint256 pollIndex, address operator);
// Final aggregated result
event FulfillAIEvaluation(bytes32 indexed aggRequestId, uint256[] aggregated, string justifications);
// Bonus payment to clustered oracle
event BonusPayment(address indexed operator, uint256 bonusFee);
// Evaluation failed or timed out
event EvaluationFailed(bytes32 indexed aggRequestId, string phase);
event EvaluationTimedOut(bytes32 indexed aggRequestId);
Usage Examples¶
Basic Integration¶
pragma solidity ^0.8.21;
interface IReputationAggregator {
function requestAIEvaluationWithApproval(
string[] memory cids,
string memory addendumText,
uint256 _alpha,
uint256 _maxOracleFee,
uint256 _estimatedBaseCost,
uint256 _maxFeeBasedScalingFactor,
uint64 _requestedClass
) external returns (bytes32);
function getEvaluation(bytes32 aggId)
external view returns (uint256[] memory, string memory, bool);
}
contract DisputeResolution {
IReputationAggregator public aggregator;
IERC20 public linkToken;
struct Dispute {
bytes32 aggregatorId;
address plaintiff;
address defendant;
uint256 amount;
bool resolved;
}
mapping(bytes32 => Dispute) public disputes;
constructor(address _aggregator, address _linkToken) {
aggregator = IReputationAggregator(_aggregator);
linkToken = IERC20(_linkToken);
}
function createDispute(
string[] memory evidenceCIDs,
string memory description,
address defendant
) external payable returns (bytes32) {
// Approve LINK tokens for aggregator
uint256 maxFee = 0.5 ether; // 0.5 LINK maximum
linkToken.approve(address(aggregator), maxFee);
// Request AI evaluation
bytes32 aggId = aggregator.requestAIEvaluationWithApproval(
evidenceCIDs,
description,
500, // 50% weight on timeliness vs quality
0.1 ether, // max 0.1 LINK per oracle
0.01 ether, // estimated base cost
5, // max 5x fee scaling
1 // standard dispute class
);
// Store dispute
disputes[aggId] = Dispute({
aggregatorId: aggId,
plaintiff: msg.sender,
defendant: defendant,
amount: msg.value,
resolved: false
});
return aggId;
}
function resolveDispute(bytes32 aggregatorId) external {
Dispute storage dispute = disputes[aggregatorId];
require(!dispute.resolved, "Already resolved");
(uint256[] memory likelihoods, string memory justification, bool exists) =
aggregator.getEvaluation(aggregatorId);
require(exists, "Evaluation not complete");
// Resolve based on first likelihood score
if (likelihoods[0] > 500) { // > 50% likelihood favoring plaintiff
payable(dispute.plaintiff).transfer(dispute.amount);
} else {
payable(dispute.defendant).transfer(dispute.amount);
}
dispute.resolved = true;
}
}
Frontend Integration¶
import { ethers } from 'ethers';
class VerdiktaDispatcher {
constructor(contractAddress, signer) {
this.contract = new ethers.Contract(
contractAddress,
REPUTATION_AGGREGATOR_ABI,
signer
);
}
async submitDispute(evidenceFiles, description, options = {}) {
try {
// Upload evidence to IPFS and get CIDs
const cids = await Promise.all(
evidenceFiles.map(file => this.uploadToIPFS(file))
);
// Get fee estimate
const maxFee = options.maxFee || ethers.parseEther("0.1");
const totalFee = await this.contract.maxTotalFee(maxFee);
// Approve LINK tokens
const linkToken = new ethers.Contract(
LINK_TOKEN_ADDRESS,
LINK_ABI,
this.contract.signer
);
await linkToken.approve(this.contract.target, totalFee);
// Submit request
const tx = await this.contract.requestAIEvaluationWithApproval(
cids,
description,
options.alpha || 500,
maxFee,
options.baseCost || ethers.parseEther("0.01"),
options.scalingFactor || 5,
options.oracleClass || 1
);
console.log("Transaction submitted:", tx.hash);
const receipt = await tx.wait();
// Extract aggregator ID from events
const event = receipt.logs.find(
log => log.topics[0] === this.contract.interface.getEvent("RequestAIEvaluation").topicHash
);
const aggId = event.topics[1];
return {
aggregatorId: aggId,
transactionHash: tx.hash,
blockNumber: receipt.blockNumber
};
} catch (error) {
console.error("Failed to submit dispute:", error);
throw error;
}
}
async getResult(aggregatorId) {
const [likelihoods, justificationCID, exists] =
await this.contract.getEvaluation(aggregatorId);
if (!exists) {
return { status: 'pending' };
}
return {
status: 'complete',
likelihoods: likelihoods.map(l => Number(l)),
justificationCID,
justification: await this.fetchFromIPFS(justificationCID)
};
}
watchProgress(aggregatorId, callbacks = {}) {
const filters = {
commitReceived: this.contract.filters.CommitReceived(aggregatorId),
commitComplete: this.contract.filters.CommitPhaseComplete(aggregatorId),
responseRecorded: this.contract.filters.NewOracleResponseRecorded(),
fulfilled: this.contract.filters.FulfillAIEvaluation(aggregatorId),
failed: this.contract.filters.EvaluationFailed(aggregatorId)
};
Object.entries(filters).forEach(([event, filter]) => {
if (callbacks[event]) {
this.contract.on(filter, callbacks[event]);
}
});
return () => {
Object.values(filters).forEach(filter => {
this.contract.off(filter);
});
};
}
}
Security Considerations¶
Commit-Reveal Protection¶
- Front-running Prevention: Oracles cannot see other responses during commit phase
- Hash Verification: Reveals must match committed hashes exactly
- Salt Randomization: Each oracle uses unique salt for additional security
Economic Security¶
- Stake Requirements: Oracles must stake VDKA tokens to participate
- Slashing Mechanism: Poor performance results in stake reduction
- Bonus Alignment: Rewards encourage honest consensus participation
Operational Security¶
- Timeout Handling: Automatic progression when oracles fail to respond
- Reputation Tracking: Long-term performance affects selection probability
- Access Control: Only approved contracts can request evaluations
Fee Structure¶
The total cost for a ReputationAggregator request includes:
- Base Fees: K × oracle_fee (for commit phase)
- Reveal Fees: M × oracle_fee (for reveal phase)
- Bonus Pool: P × bonus_multiplier × oracle_fee
Maximum Total: fee × (K + M + P × bonus_multiplier)
With default settings (K=5, M=4, P=2, B=3): - Maximum: fee × (5 + 4 + 2 × 3) = fee × 15
- Typical: Much lower as not all oracles receive bonuses
Troubleshooting¶
Common Issues¶
"Insufficient LINK tokens"
// Check required amount first
const totalFee = await contract.maxTotalFee(maxOracleFee);
await linkToken.approve(contractAddress, totalFee);
"No active oracles available" - Ensure requested oracle class exists - Check if maxOracleFee is sufficient - Verify oracles aren't all locked/blocked
"Evaluation timed out"
// Monitor timeout and handle gracefully
const timeout = await contract.responseTimeoutSeconds();
setTimeout(() => {
contract.finalizeEvaluationTimeout(aggregatorId);
}, timeout * 1000);
Error Codes¶
Error | Cause | Solution |
---|---|---|
Empty CID list | No evidence provided | Include at least one IPFS hash |
Too many CIDs | > 10 evidence files | Combine evidence or split requests |
CID too long | Individual CID > 100 chars | Use valid IPFS hashes |
Addendum too long | Description > 1000 chars | Shorten description |
K must be >= M | Invalid phase configuration | Use valid K,M,N,P values |
Best Practices¶
- Always check fee requirements before submitting requests
- Monitor events to track progress and handle timeouts
- Use appropriate alpha values based on use case priority
- Handle both success and failure cases in your application
- Cache IPFS content for better user experience
- Implement retry logic for network failures