.claude/skills/resupply-rewards/SKILL.md
This skill should be used when the user asks about "rewards", "RewardHandler", "claim rewards", "RSUP emissions", "reward distribution", "multi-epoch rewards", "protocol emissions", or needs to understand Resupply's reward system.
npx skillsauth add cyotee/crane resupply-rewardsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The reward system distributes RSUP emissions and protocol fees across lending pairs, insurance pool, and stakers. Key contracts in /src/protocol/RewardHandler.sol, /src/protocol/RewardDistributorMultiEpoch.sol, and /src/dao/emissions/EmissionsController.sol.
┌─────────────────────┐
│ EmissionsController │
│ (RSUP minting) │
└─────────┬───────────┘
│
┌─────────▼───────────┐
│ RewardHandler │
│ (distribution hub) │
└─────────┬───────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Lending Pairs │ │ Insurance Pool │ │ GovStaker │
│ (borrowers) │ │ (reIP holders)│ │ (RSUP stakers) │
└───────────────┘ └─────────────────┘ └─────────────────┘
Central hub for reward distribution (/src/protocol/RewardHandler.sol):
contract RewardHandler {
// Distribution weights (in basis points, 10000 = 100%)
uint256 public pairWeight; // e.g., 7000 (70%)
uint256 public insuranceWeight; // e.g., 1000 (10%)
uint256 public stakerWeight; // e.g., 2000 (20%)
// Reward token tracking
address[] public rewardTokens;
// Distribution function
function distributeRewards() external;
function notifyReward(address token, uint256 amount) external;
}
function distributeRewards() external {
for (uint256 i = 0; i < rewardTokens.length; i++) {
address token = rewardTokens[i];
uint256 balance = IERC20(token).balanceOf(address(this));
if (balance > 0) {
// Calculate splits
uint256 toPairs = balance * pairWeight / 10000;
uint256 toInsurance = balance * insuranceWeight / 10000;
uint256 toStakers = balance - toPairs - toInsurance;
// Distribute to pairs (proportional to debt)
_distributeToPairs(token, toPairs);
// Send to insurance pool
_sendToInsurance(token, toInsurance);
// Send to staker rewards
_sendToStakers(token, toStakers);
}
}
}
Base reward tracking (/src/protocol/RewardDistributorMultiEpoch.sol):
contract RewardDistributorMultiEpoch {
// Per-epoch reward accumulation
mapping(address token => mapping(uint256 epoch => uint256 rewards)) public epochRewards;
// Cumulative reward tracking
mapping(address token => uint256) public rewardIntegral;
mapping(address user => mapping(address token => uint256)) public userRewardIntegral;
mapping(address user => mapping(address token => uint256)) public claimableRewards;
// Checkpoint user rewards
function _checkpoint(address _user) internal;
// Calculate earned rewards
function earned(address _user, address _token) public view returns (uint256);
}
// When rewards are added:
function _notifyReward(address _token, uint256 _amount) internal {
uint256 supply = totalSupply();
if (supply > 0) {
// Increase reward per token
rewardIntegral[_token] += (_amount * 1e18) / supply;
}
epochRewards[_token][getEpoch()] += _amount;
}
// When user checkpoints:
function _checkpoint(address _user) internal {
uint256 balance = balanceOf(_user);
for (uint256 i = 0; i < rewardTokens.length; i++) {
address token = rewardTokens[i];
uint256 integral = rewardIntegral[token];
uint256 userIntegral = userRewardIntegral[_user][token];
// Calculate new rewards since last checkpoint
uint256 newRewards = (balance * (integral - userIntegral)) / 1e18;
claimableRewards[_user][token] += newRewards;
userRewardIntegral[_user][token] = integral;
}
}
Controls RSUP token emissions (/src/dao/emissions/EmissionsController.sol):
contract EmissionsController {
// Emission schedule (per year)
uint256[] public yearlyEmissions;
// Year 1: 50M RSUP
// Year 2: 40M RSUP
// Year 3: 30M RSUP
// Year 4: 20M RSUP
// Year 5+: 10M RSUP (tail emissions)
uint256 public startTime;
uint256 public lastMintTime;
// Mint pending emissions
function mint() external returns (uint256 minted);
}
function getEmissionRate() public view returns (uint256 perSecond) {
uint256 year = (block.timestamp - startTime) / 365 days;
uint256 yearlyAmount;
if (year < yearlyEmissions.length) {
yearlyAmount = yearlyEmissions[year];
} else {
yearlyAmount = tailEmissionRate; // Perpetual tail
}
perSecond = yearlyAmount / 365 days;
}
function mint() external returns (uint256 minted) {
uint256 elapsed = block.timestamp - lastMintTime;
uint256 rate = getEmissionRate();
minted = elapsed * rate;
lastMintTime = block.timestamp;
IGovToken(govToken).mint(rewardHandler, minted);
}
Protocol inflation distributed to participants:
Total RSUP Supply: 1,000,000,000
Year 1 emissions: 50,000,000 (5%)
Year 2 emissions: 40,000,000 (4%)
...
Tail emissions: 10,000,000/year (1%)
Fee split from borrower interest payments:
// When interest accrues in pair:
function _accrue() internal {
uint256 interest = calculateInterest();
uint256 protocolFee = interest * protocolFeeRate / 1e18;
// Send fee to reward handler
IRewardHandler(rewardHandler).notifyReward(asset, protocolFee);
}
Fees from collateral redemptions:
// In RedemptionHandler:
function _distributeFees(uint256 fee) internal {
uint256 protocolShare = fee * protocolRedemptionFee / 1e18;
IRewardHandler(rewardHandler).notifyReward(asset, protocolShare);
}
From Convex integration:
// Pairs with Convex integration receive:
// - CRV rewards
// - CVX rewards
// Distributed to pair depositors
// Claim all rewards from a pair
IResupplyPair(pair).getReward(userAddress, forwardTo);
// Claim insurance pool rewards
IInsurancePool(insurancePool).getReward(userAddress);
// Claim staking rewards
IGovStaker(staker).getReward(userAddress);
function claimAll(address _user) external {
// Claim from all pairs
address[] memory pairs = registry.getAllPairs();
for (uint256 i = 0; i < pairs.length; i++) {
IResupplyPair(pairs[i]).getReward(_user, _user);
}
// Claim from insurance
insurancePool.getReward(_user);
// Claim from staking
staker.getReward(_user);
}
Default distribution weights:
| Recipient | Weight | Description | |-----------|--------|-------------| | Borrowers (pairs) | 70% | Proportional to debt | | Stakers | 20% | RSUP staking rewards | | Insurance | 10% | reIP holder rewards |
event RewardDistributed(
address indexed token,
uint256 toPairs,
uint256 toInsurance,
uint256 toStakers
);
event RewardClaimed(
address indexed user,
address indexed token,
uint256 amount
);
event EmissionsMinted(
uint256 amount,
uint256 epoch
);
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
documentation
Write to contracts and send transactions. Use when executing state-changing contract functions.
development
HTTP and WebSocket transports for blockchain connectivity. Use when configuring network connections.
data-ai
Read contract data with type-safe ABI. Use when querying smart contract view/pure functions.