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
Rebasecontract interacts with a dynamically createdReTokencontract specific to the staked token. ThisReTokencontract then mints newreTokensto the user, representing their staked position. - Dynamic
ReTokenCreation: For each unique token staked, theRebasecontract deploys a minimal proxy (cloneDeterministic) of theReTokencontract. This ensures that each staked token has its own dedicatedreTokenthat tracks its rebasing logic. - Application Integration: The
Rebasecontract allows for integration with various "applications" (represented by smart contract addresses). When a user stakes, they specify anappaddress. TheRebasecontract then calls theonStakefunction on the specifiedappcontract, notifying it of the new stake. - Unstaking Tokens: Users can unstake their tokens, which involves burning the
reTokensand transferring the original staked tokens back to the user. Similarly, theonUnstakefunction on theappcontract 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
Splittercontract is initialized with a specificrewardToken(an ERC20 token) that it will distribute. - Distributors: Only authorized
distributors(addresses added by the contract owner) can sendrewardTokento theSplittercontract for distribution. StakeTrackerIntegration: TheSplittercontract uses aStakeTrackercontract to keep track of user stakes and reward distributions over time.- When a
distributorsendsrewardTokenvia thesplitfunction, theSplitterrecords this reward in theStakeTrackerby 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
Rebasecontract, theSplitter(as anappofRebase) receivesonStakeandonUnstakecalls, and updates theStakeTrackeraccordingly.
- When a
- Claiming Rewards: Users can
claimtheir accumulated rewards. Theclaimfunction calculates a user's share of rewards based on their proportional stake at each snapshot when rewards were distributed. It then transfers therewardTokento the user. To prevent excessive gas usage, users can specify alimiton 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
rewardQuantityreceived by theSplitterat that snapshot. - Calculating User Rewards: The
getUserRewardfunction 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 theRebasecontract. Note the address of the deployedRebasecontract.- Example on BaseScan: 0x89fA20b30a88811FBB044821FEC130793185c60B
- Deploy
Splitter.sol: Deploy theSplittercontract, 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
Rebasecontract to spend your ERC20 tokens:IERC20(YOUR_TOKEN_ADDRESS).approve(REBASE_CONTRACT_ADDRESS, AMOUNT_TO_STAKE);
- Call the
stakefunction on theRebasecontract:(ReplaceRebase.stake(YOUR_TOKEN_ADDRESS, AMOUNT_TO_STAKE, SPLITTER_CONTRACT_ADDRESS);YOUR_TOKEN_ADDRESS,AMOUNT_TO_STAKE, andSPLITTER_CONTRACT_ADDRESSwith actual values.)
To stake ETH:
- Call the
stakeETHfunction on theRebasecontract, sending the desired ETH amount:(ReplaceRebase.stakeETH(SPLITTER_CONTRACT_ADDRESS) payable; // send value with the transaction
SPLITTER_CONTRACT_ADDRESSwith 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
Splittercontract must add your address as a distributor:Splitter.addDistributor(YOUR_DISTRIBUTOR_ADDRESS); - Approve the
Splittercontract to spend the reward tokens from your distributor address:IERC20(REWARD_TOKEN_ADDRESS).approve(SPLITTER_CONTRACT_ADDRESS, AMOUNT_OF_REWARD);
- Call the
splitfunction on theSplittercontract 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);