Incentives

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.

ARCH

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.
CLAIM

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 }.
API

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.

EVENT

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.

REG

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.

ADDR

Deployed Addresses

RewardsDistributor per network

Ethereum (1):0xd09931d9A7A320B5D0d407D47f28A269c08Ce04D

Additional networks will be listed here as RewardsDistributor instances are deployed.