This repository contains two core Solidity smart contracts: Rebase.sol
(Staking Contract) and Splitter.sol
(Distribution Contract), along with a helper contract StakeTracker.sol
. These contracts work together to enable users to stake various ERC20 tokens (and ETH) and receive rewards distributed by authorized entities..
The Rebase
contract serves as the primary staking hub. It allows users to stake ERC20 tokens or ETH, and in return, they receive a corresponding amount of "reTokens" (rebasing tokens).
- Staking Tokens: Users can stake any ERC20 token or native ETH (which is converted to WETH internally). When tokens are staked, the
Rebase
contract interacts with a dynamically createdReToken
contract specific to the staked token. ThisReToken
contract then mints newreTokens
to the user, representing their staked position. - Dynamic
ReToken
Creation: For each unique token staked, theRebase
contract deploys a minimal proxy (cloneDeterministic
) of theReToken
contract. This ensures that each staked token has its own dedicatedreToken
that tracks its rebasing logic. - Application Integration: The
Rebase
contract allows for integration with various "applications" (represented by smart contract addresses). When a user stakes, they specify anapp
address. TheRebase
contract then calls theonStake
function on the specifiedapp
contract, notifying it of the new stake. - Unstaking Tokens: Users can unstake their tokens, which involves burning the
reTokens
and transferring the original staked tokens back to the user. Similarly, theonUnstake
function on theapp
contract is called. - Restaking Tokens: Users can transfer their staked position from one application to another without fully unstaking and restaking.
The Splitter
contract is responsible for receiving reward tokens and distributing them proportionally to users who have staked through the Rebase
contract and linked to this Splitter
as their app
.
- Reward Token: The
Splitter
contract is initialized with a specificrewardToken
(an ERC20 token) that it will distribute. - Distributors: Only authorized
distributors
(addresses added by the contract owner) can sendrewardToken
to theSplitter
contract for distribution. StakeTracker
Integration: TheSplitter
contract uses aStakeTracker
contract to keep track of user stakes and reward distributions over time.- When a
distributor
sendsrewardToken
via thesplit
function, theSplitter
records this reward in theStakeTracker
by creating a "snapshot." This snapshot captures the total supply of staked tokens at that moment and the amount of reward distributed. - When users stake or unstake via the
Rebase
contract, theSplitter
(as anapp
ofRebase
) receivesonStake
andonUnstake
calls, and updates theStakeTracker
accordingly.
- When a
- Claiming Rewards: Users can
claim
their accumulated rewards. Theclaim
function calculates a user's share of rewards based on their proportional stake at each snapshot when rewards were distributed. It then transfers therewardToken
to the user. To prevent excessive gas usage, users can specify alimit
on the number of snapshots considered in a single claim transaction.
The StakeTracker
contract is a specialized ERC20Snapshot token that acts as a ledger for tracking user stakes and reward allocations.
- ERC20Snapshot: It extends OpenZeppelin's
ERC20Snapshot
, allowing it to record balances and total supply at specific points in time (snapshots). - Tracking Rewards: It maps snapshot IDs to the
rewardQuantity
received by theSplitter
at that snapshot. - Calculating User Rewards: The
getUserReward
function calculates a user's proportional share of rewards for a given snapshot by comparing theirbalanceOfAt
(their stake at that snapshot) to thetotalSupplyAt
(total staked supply at that snapshot).
- Deploy
Rebase.sol
: Deploy theRebase
contract. Note the address of the deployedRebase
contract.- Example on BaseScan: 0x89fA20b30a88811FBB044821FEC130793185c60B
- Deploy
Splitter.sol
: Deploy theSplitter
contract, providing thestakeToken
(the token users will stake) andrewardToken
(the token that will be distributed as rewards) addresses as constructor arguments.- Example on BaseScan: 0x6bc86cb06db133e939cc9d3cd27b6b34772dd0cb
To stake ERC20 tokens:
- Approve the
Rebase
contract to spend your ERC20 tokens:IERC20(YOUR_TOKEN_ADDRESS).approve(REBASE_CONTRACT_ADDRESS, AMOUNT_TO_STAKE);
- Call the
stake
function on theRebase
contract:(ReplaceRebase.stake(YOUR_TOKEN_ADDRESS, AMOUNT_TO_STAKE, SPLITTER_CONTRACT_ADDRESS);
YOUR_TOKEN_ADDRESS
,AMOUNT_TO_STAKE
, andSPLITTER_CONTRACT_ADDRESS
with actual values.)
To stake ETH:
- Call the
stakeETH
function on theRebase
contract, sending the desired ETH amount:(ReplaceRebase.stakeETH(SPLITTER_CONTRACT_ADDRESS) payable; // send value with the transaction
SPLITTER_CONTRACT_ADDRESS
with the actual value.)
To unstake ERC20 tokens:
Rebase.unstake(YOUR_TOKEN_ADDRESS, AMOUNT_TO_UNSTAKE, SPLITTER_CONTRACT_ADDRESS);
To unstake ETH:
Rebase.unstakeETH(AMOUNT_TO_UNSTAKE, SPLITTER_CONTRACT_ADDRESS);
Only approved distributors can perform this action.
- The owner of the
Splitter
contract must add your address as a distributor:Splitter.addDistributor(YOUR_DISTRIBUTOR_ADDRESS);
- Approve the
Splitter
contract to spend the reward tokens from your distributor address:IERC20(REWARD_TOKEN_ADDRESS).approve(SPLITTER_CONTRACT_ADDRESS, AMOUNT_OF_REWARD);
- Call the
split
function on theSplitter
contract to distribute rewards:Splitter.split(AMOUNT_OF_REWARD);
Any user can claim their earned rewards.
Splitter.claim(YOUR_RECEIVING_ADDRESS, MAX_SNAPSHOTS_TO_PROCESS);
(Replace YOUR_RECEIVING_ADDRESS
with the address where you want to receive the rewards, and MAX_SNAPSHOTS_TO_PROCESS
with a reasonable number to avoid gas limits, e.g., 50 or 100.)
You can check your unclaimed earnings before claiming:
Splitter.getUnclaimedEarnings(YOUR_ADDRESS, MAX_SNAPSHOTS_TO_PROCESS);