Rewards
CapyFi distributes off-chain-computed campaign rewards through a Merkle-based on-chain claim contract. The off-chain pipeline publishes a Merkle root per campaign; users claim with a proof.
Introduction
What rewards cover
The Rewards system pays out incentives that are computed off-chain — typically per-campaign attribution based on supply, borrow, or referral activity. Each campaign has an identifier (bytes32 campaignId) and a reward token.
Cumulative entitlements per (campaignId, account, rewardToken) are encoded into a Merkle tree. The tree's root is published on-chain by an authorized publisher and users claim by submitting a proof against it.
Architecture
Components
- RewardsDistributor: on-chain contract that holds the Merkle root per campaign, accounts claimed amounts, transfers reward tokens, and exposes role-based admin (
ROOT_PUBLISHER_ROLE,DEFAULT_ADMIN_ROLE). - CampaignRegistry: companion contract reachable via
distributor.campaignRegistry(). Tracks campaign metadata. - Off-chain rewards service: computes entitlements, builds Merkle trees, posts roots on-chain, and serves proofs to client integrations.
Claiming
How users withdraw their rewards
The claim flow is cumulative-based: the Merkle leaf encodes the total amount earned by an account in a campaign for a given reward token. The contract subtracts what was already claimed and transfers the difference, so re-submitting an old proof after a new root is published is harmless — the user simply gets the delta.
Two on-chain claim paths are available:
claim()— single campaign / token.claimMultiple()— batched array of{ campaignId, account, rewardToken, cumulativeAmount, merkleProof }.
RewardsDistributor Interface
Methods exposed by the contract
Reads
function merkleRoots(bytes32 campaignId) view returns (bytes32)function getMerkleRoot(bytes32 campaignId) view returns (bytes32)function claimed(bytes32 campaignId, address account, address rewardToken) view returns (uint256)function getClaimed(bytes32 campaignId, address account, address rewardToken) view returns (uint256)function getClaimable(bytes32 campaignId, address account, address rewardToken, uint256 cumulativeAmount, bytes32[] merkleProof) view returns (uint256)function verifyProof(bytes32 campaignId, address account, address rewardToken, uint256 cumulativeAmount, bytes32[] merkleProof) view returns (bool)function campaignRegistry() view returns (address)Writes
function claim(bytes32 campaignId, address account, address rewardToken, uint256 cumulativeAmount, bytes32[] merkleProof) returns (uint256 claimedAmount)function claimMultiple((bytes32,address,address,uint256,bytes32[])[] claims) returns (uint256 totalClaimed)function updateMerkleRoot(bytes32 campaignId, bytes32 newRoot)updateMerkleRoot is gated by ROOT_PUBLISHER_ROLE. Role admin lives at DEFAULT_ADMIN_ROLE.
Events
Important events
MerkleRootUpdated
event MerkleRootUpdated(bytes32 indexed campaignId, bytes32 oldRoot, bytes32 newRoot)Emitted when the publisher rotates a campaign's Merkle root.
Claimed
event Claimed(bytes32 indexed campaignId, address indexed account, address indexed rewardToken, uint256 amount)Emitted on each successful claim with the delta amount transferred.
Campaign Registry
Companion contract
The CampaignRegistry stores per-campaign metadata used by the off-chain rewards service. Its address can be discovered at runtime by calling rewardsDistributor.campaignRegistry() on any deployed RewardsDistributor.
Deployed Addresses
RewardsDistributor per network
0xd09931d9A7A320B5D0d407D47f28A269c08Ce04DAdditional networks will be listed here as RewardsDistributor instances are deployed.