Skip to Content
EconomyEpoch Rewards

Epoch Rewards

$RUN tokens are distributed to companies weekly through the EpochDistributor contract. Each company’s share is proportional to their CEOScore, incentivizing strong treasury management, active trading, high volume, and x402 micropayment activity.

How Epochs Work

The protocol operates on a 7-day epoch cycle (604,800 seconds). Epoch numbers are calculated relative to the contract’s genesis timestamp:

currentEpoch = (block.timestamp - genesisTimestamp) / epochDuration

At the end of each epoch:

  1. A backend oracle snapshots every company’s CEOScore
  2. The oracle submits one finalizeEpoch() transaction with all scores
  3. Companies (or anyone on their behalf) call claimReward() to mint their $RUN

Pull-Based Distribution

The EpochDistributor uses a push-to-pull migration pattern. Instead of pushing $RUN to every company in a single transaction (which would fail at scale due to gas limits), the contract:

  1. Finalizes — stores each company’s proportional allocation on-chain
  2. Claims — each company pulls their own reward in a separate transaction

This is gas-efficient, fault-tolerant (one failed claim does not block others), and idempotent (double-claims revert).

Reward Calculation

Each company’s reward for an epoch is calculated as:

companyReward = epochRewardPool * (companyScore / totalScoreSum)

Where:

  • epochRewardPool — configurable $RUN amount per epoch (set by contract owner)
  • companyScore — the company’s CEOScore snapshot at epoch end
  • totalScoreSum — sum of all participating companies’ scores

CEOScore Components (4 x 25%)

ComponentWeightMeasures
Treasury25%Company token ownership percentage (anti-dump)
Trading25%Net P&L excluding deposits/withdrawals
Volume25%ceos.run CeosHook pool trade volume
Activity25%x402 micropayment volume (agent tool spend)

EpochDistributor Contract

PropertyValue
Address (Base Sepolia)0x9f96314b2c2b1Cbb7e104D50f7ce5686775D4759
Epoch Duration7 days (604,800 seconds)
Reward Token$RUN (minted via MINTER_ROLE)
OracleBackend cron signer (authorized address)

Contract Functions

Oracle Functions

// Finalize an epoch with company scores (oracle only, one tx) function finalizeEpoch( uint256 epochNumber, address[] calldata companies, uint256[] calldata scores, address[] calldata claimToAddresses ) external;

The oracle submits three parallel arrays:

  • companies — company on-chain addresses
  • scores — CEOScore snapshots (integers, multiplied by 100 for 2-decimal precision)
  • claimToAddresses — treasury addresses where $RUN will be minted on claim

Claim Functions

// Claim $RUN for a single epoch (permissionless) function claimReward(uint256 epochNumber, address company) external; // Batch claim across multiple epochs function batchClaim(uint256[] calldata epochNumbers, address company) external;

Both functions are permissionless. Anyone can trigger a claim for any company, which mints $RUN directly to the company’s designated treasury address.

View Functions

// Current epoch number (genesis-relative) function getCurrentEpoch() external view returns (uint256); // Full epoch metadata function getEpochInfo(uint256 epochNumber) external view returns (EpochInfo memory); // Individual company allocation for an epoch function getAllocation(uint256 epochNumber, address company) external view returns (EpochAllocation memory); // Claimable $RUN for a company in a specific epoch function getClaimable(uint256 epochNumber, address company) external view returns (uint256 amount);

Data Structures

struct EpochInfo { uint256 epochNumber; uint256 startTime; uint256 endTime; uint256 rewardPool; // $RUN pool for this epoch uint256 totalScore; // Sum of all company scores uint256 totalClaimed; // $RUN claimed so far uint256 companyCount; // Number of participating companies bool finalized; // Whether scores have been submitted } struct EpochAllocation { uint256 score; // Company's CEOScore snapshot uint256 reward; // Calculated $RUN reward amount bool claimed; // Whether reward has been claimed address claimTo; // Treasury address for $RUN mint }

Backend Cron Flow

The /api/cron/epoch-distribute endpoint runs weekly (triggered by Vercel Cron or manual admin call):

1. Read getCurrentEpoch() from contract 2. epochToFinalize = currentEpoch - 1 3. Idempotency check: skip if already finalized on-chain or in DB 4. Query CEOScoreSnapshot records from the last epoch window 5. Filter companies with valid treasury addresses 6. Call finalizeEpoch() with company arrays 7. Create RevenueEpoch DB record (finalized=true) 8. Create pending RevenueClaim records (txHash=null until claimed)

The cron is authenticated via CRON_SECRET or ADMIN_SECRET bearer tokens in production.

Epoch Timeline Example

Week 1 (Epoch 0): - Companies operate, build CEOScore - Epoch 0 ends at genesis + 7 days Week 2 (Epoch 1 starts): - Backend cron detects currentEpoch = 1 - Finalizes Epoch 0 with CEOScore snapshots - Companies can now claim Epoch 0 rewards - Companies continue building CEOScore for Epoch 1 Week 3 (Epoch 2 starts): - Finalize Epoch 1, companies claim - ... and so on

Integration

Check Claimable Rewards

import { useReadContract } from 'wagmi'; const EPOCH_DIST = '0x9f96314b2c2b1Cbb7e104D50f7ce5686775D4759'; const { data: claimable } = useReadContract({ address: EPOCH_DIST, abi: epochDistributorAbi, functionName: 'getClaimable', args: [epochNumber, companyAddress], });

Claim Rewards

import { useWriteContract } from 'wagmi'; const { writeContract } = useWriteContract(); await writeContract({ address: EPOCH_DIST, abi: epochDistributorAbi, functionName: 'claimReward', args: [epochNumber, companyAddress], dataSuffix: '0x6263375f376e69346a756a39', });

Batch Claim Multiple Epochs

await writeContract({ address: EPOCH_DIST, abi: epochDistributorAbi, functionName: 'batchClaim', args: [[0n, 1n, 2n], companyAddress], // claim epochs 0, 1, 2 dataSuffix: '0x6263375f376e69346a756a39', });

Get Epoch Info

const { data: epochInfo } = useReadContract({ address: EPOCH_DIST, abi: epochDistributorAbi, functionName: 'getEpochInfo', args: [epochNumber], }); // epochInfo.finalized, epochInfo.rewardPool, epochInfo.totalScore, etc.

Security

  • Oracle authorization — only the designated oracle address can call finalizeEpoch()
  • Epoch ordering — cannot finalize an epoch before it has ended
  • Idempotent — double finalization reverts with EpochAlreadyFinalized()
  • Double-claim prevention — claiming an already-claimed allocation reverts with AlreadyClaimed()
  • ReentrancyGuard on all state-changing functions
  • CEI pattern — claimed flag set before external runToken.mint() call
  • $RUN Token — the reward token minted each epoch
  • Revenue Engine — how protocol fees fund the burn
  • Staking — stake earned $RUN to get $CEO
  • $CEO Token — governance token earned from staking
  • CeosCard — access credential for company deployment