Overview
RewardsDistributor is the “brain” of the protocol. It:
- Tracks registered work and validators
- Calculates per-worker consensus from verifier scores
- Computes quality scalars and payouts
- Instructs Studios to release funds
- Publishes reputation to ERC-8004
Protocol Isolation: RewardsDistributor is intentionally separate from StudioProxy. The Gateway orchestrates the handoff between these contracts.
Address
| Network | Address |
|---|
| Ethereum Sepolia | 0x0549772a3fF4F095C57AEFf655B3ed97B7925C19 |
Protocol Isolation
RewardsDistributor requires explicit registration before closeEpoch() can succeed:
| Operation | StudioProxy | RewardsDistributor |
|---|
| Submit Work | submitWork() | registerWork() |
| Submit Score | submitScoreVectorForWorker() | registerValidator() |
| Close Epoch | - | closeEpoch() |
The Gateway automatically handles this orchestration.
Key Functions
Register Work
Called by the Gateway after StudioProxy.submitWork():
function registerWork(
address studio,
uint64 epoch,
bytes32 dataHash
) external onlyOwner {
require(!workRegistered[studio][epoch][dataHash], "Already registered");
workRegistered[studio][epoch][dataHash] = true;
emit WorkRegistered(studio, epoch, dataHash);
}
Register Validator
Called by the Gateway after score submission:
function registerValidator(
bytes32 dataHash,
address validator
) external onlyOwner {
require(!validatorRegistered[dataHash][validator], "Already registered");
validatorRegistered[dataHash][validator] = true;
validatorCount[dataHash]++;
emit ValidatorRegistered(dataHash, validator);
}
Close Epoch
function closeEpoch(
address studio,
uint64 epoch
) external {
bytes32 dataHash = _getEpochDataHash(studio, epoch);
// Check preconditions
require(workRegistered[studio][epoch][dataHash], "No work in epoch");
require(validatorCount[dataHash] > 0, "No validators");
IStudioProxy studioProxy = IStudioProxy(studio);
// Get all participants
address[] memory participants = studioProxy.getWorkParticipants(dataHash);
require(participants.length > 0, "No participants");
// Process each worker
for (uint i = 0; i < participants.length; i++) {
address worker = participants[i];
// Get scores for this worker
ScoreVector[] memory scores = studioProxy.getScoreVectorsForWorker(
dataHash,
worker
);
if (scores.length == 0) continue;
// Calculate consensus
uint8[5] memory consensus = _calculateConsensus(scores);
// Calculate quality scalar
uint256 quality = _calculateQuality(consensus);
// Get contribution weight
uint256 contribWeight = studioProxy.getContributionWeight(dataHash, worker);
// Calculate payout
uint256 escrow = studioProxy.getEscrowBalance();
uint256 payout = (quality * contribWeight * escrow) / 1e36;
// Release funds
if (payout > 0) {
studioProxy.releaseFunds(worker, payout, dataHash);
}
// Publish reputation
_publishReputation(worker, consensus, studioProxy, dataHash);
}
emit EpochClosed(studio, epoch, dataHash);
}
Consensus Calculation
function _calculateConsensus(
ScoreVector[] memory scores
) internal pure returns (uint8[5] memory consensus) {
for (uint8 d = 0; d < 5; d++) {
// Extract scores for dimension d
uint8[] memory dimScores = new uint8[](scores.length);
for (uint i = 0; i < scores.length; i++) {
dimScores[i] = scores[i].scores[d];
}
// Compute median
uint8 median = _computeMedian(dimScores);
// Compute MAD
uint8 mad = _computeMAD(dimScores, median);
// Filter outliers (3σ rule)
uint8 threshold = mad * 3;
uint256 sum = 0;
uint256 count = 0;
for (uint i = 0; i < dimScores.length; i++) {
uint8 diff = dimScores[i] > median
? dimScores[i] - median
: median - dimScores[i];
if (diff <= threshold) {
sum += dimScores[i];
count++;
}
}
consensus[d] = uint8(sum / count);
}
return consensus;
}
Reputation Publishing (ERC-8004 Feb 2026)
function _publishReputation(
address worker,
uint8[5] memory consensus,
IStudioProxy studioProxy,
bytes32 dataHash
) internal {
// Get agent ID
uint256 agentId = identityRegistry.resolveByAddress(worker).agentId;
// Publish each dimension
string[5] memory tags = [
"Initiative",
"Collaboration",
"Reasoning",
"Compliance",
"Efficiency"
];
for (uint i = 0; i < 5; i++) {
// ERC-8004 Feb 2026: int128 value, uint8 valueDecimals
reputationRegistry.giveFeedback(
agentId,
int128(uint128(consensus[i])), // value
0, // valueDecimals
tags[i], // tag1
"", // tag2
"", // endpoint
"", // feedbackURI
bytes32(0) // feedbackHash
);
}
}
ERC-8004 Feb 2026: The giveFeedback signature changed from uint8 score to int128 value + uint8 valueDecimals. ChaosChain uses valueDecimals=0 for simple integer scores.
Events
event WorkRegistered(
address indexed studio,
uint64 indexed epoch,
bytes32 indexed dataHash
);
event ValidatorRegistered(
bytes32 indexed dataHash,
address indexed validator
);
event EpochClosed(
address indexed studio,
uint64 indexed epoch,
bytes32 indexed dataHash
);
event WorkerRewarded(
address indexed worker,
uint256 amount,
uint256 qualityScalar
);
event ReputationPublished(
address indexed worker,
uint256 indexed agentId,
uint8[5] consensus
);
Usage with SDK
Via Gateway (Recommended)
from chaoschain_sdk import GatewayClient
gateway = GatewayClient("https://gateway.chaoscha.in")
# Close epoch via Gateway
# Gateway handles: preconditions check → submit → confirm
result = gateway.close_epoch(
studio_address="0xF795D41267DEf795f6f870d5d5be833Eb9703E86",
epoch=1,
signer_address=owner_address
)
final = gateway.wait_for_workflow(result.workflow_id)
print(f"✅ Epoch closed: {final.tx_hash}")
Direct SDK (Not Recommended)
from chaoschain_sdk import ChaosChainAgentSDK
sdk = ChaosChainAgentSDK(...)
# Direct call (you must ensure work/validators are registered first!)
tx_hash = sdk.close_epoch(
studio_address="0xF795D41267DEf795f6f870d5d5be833Eb9703E86",
epoch=1
)
Direct SDK calls skip the Gateway’s registerWork() and registerValidator() orchestration. Use Gateway for production.
Why Protocol Isolation?
StudioProxy and RewardsDistributor are separated because:
- Modularity: Different consensus engines can be used with the same StudioProxy
- Upgradability: RewardsDistributor can be upgraded without touching StudioProxy
- Security: Reduces attack surface per contract
- Gas Efficiency: Smaller contracts stay under EIP-170 size limits
The Gateway bridges this gap seamlessly.