Staker is a flexible, configurable staking contract. Staker makes it easy to distribute onchain staking rewards for any ERC20 token, including DAO governance tokens.
- Staker is deployed with a single staking token.
- Staker is deployed with an admin, such as a DAO.
- Staker is configured to collect and distribute reward tokens.
- Tokenholders of the staking token can deposit those tokens in Staker.
- There is no delay to deposit or withdraw.
- If the staking token is a governance token, depositors can delegate their staked tokens' voting power to themselves or someone else.
- The depositor sets a claimer who can claim the staking rewards, such as themselves or someone else.
- The admin sends rewards into Staker.
- Optionally, the admin sets eligibility criteria for rewards.
- Staker distributes those rewards over time.
- Each tokenholder's reward is proportional to their staked balance over time.
- Claimers can claim their accrued rewards at any time.
When Staker is used for a protocol or DAO, the rewards are generally funded by protocol revenue and/or issuance of the native token from the treasury.
Staker can be deployed as an immutable contract with minimal governance. It does have some admin functions:
- Adding a new source of rewards.
- Changing the eligibility oracle or the emergency pause guardian.
- Overriding eligibility for a particular address.
The staking token can be an ERC20
token, including ERC20Votes
governance tokens. Staker splits up all voting power in Staker by creating a surrogate contract for each delegate.
Staker distributes rewards over a fixed period of time. This minimizes discontinuities from flash staking, and prevents frontrunning attacks, aimed at gaining a disproportionate share of rewards, ahead of reward distributions.
The staking system accepts user stake, delegates their voting power, and distributes rewards for eligible stakers.
stateDiagram-v2
direction TB
User --> CUF: Stakes tokens
state Staker {
state "Key User Functions" as CUF {
stake --> claimReward
claimReward --> withdraw
}
state "Key State" as KS {
rewardRate
deposits
}
state "Admin Functions" as CAF {
setRewardNotifier
setEarningPowerCalculator
}
}
state DelegationSurrogate {
state "Per Delegatee" as PD {
HoldsTokens
DelegatesVotes
}
}
KS --> DelegationSurrogate: Holds tokens per delegatee
DelegationSurrogate --> Delegatee: Delegates voting power
Admin --> CAF: e.g. governance
RewardNotifier --> Staker: Tells Staker about new rewards
EarningPowerCalculator --> Staker: Calculates eligibility
The earning power calculator determines which depositors are eligible for rewards—and the rate at which those rewards are earned—based on their stake and their governance delegatee. The calculator is a modular component of the staker, which can be customized and updated by owner of the Staker, such as a DAO. One provided implementation uses an oracle. An oracle is needed because eligibility depends on the off-chain behavior of DAO delegates.
stateDiagram-v2
direction TB
state EarningPowerCalculator {
state "Public Functions" as PF {
GetEarningPower: getEarningPower()
}
state "Score Oracle Functions" as SOF {
UpdateScore: updateDelegateeScore()
}
state "Owner Functions" as OF {
OverrideScore: overrideDelegateeScore()
SetScoreLock: setDelegateeScoreLock()
SetGuardian: setOraclePauseGuardian()
}
state "Guardian Functions" as GF {
SetOracleState: setOracleState()
}
}
ScoreOracle --> SOF: Updates scores
Owner --> OF: Admin controls
Guardian --> GF: Emergency pause
PF --> Staker: Returns earning power to staking system
These contracts were built and tested with care by the team at ScopeLift.
This project uses Foundry. Follow these instructions to install it.
Clone the repo.
Set up your .env file
cp .env.template .env
# edit the .env to fill in values
Install dependencies & run tests.
forge install
forge build
forge test
This project uses scopelint for linting and spec generation. Follow these instructions to install it.
To use scopelint's linting functionality, run:
scopelint check # check formatting
scopelint fmt # apply formatting changes
To use scopelint's spec generation functionality, run:
scopelint spec
This command will use the names of the contract's unit tests to generate a human readable spec. It will list each contract, its constituent functions, and the human readable description of functionality each unit test aims to assert.
The code in this repository is licensed under the GNU Affero General Public License unless otherwise indicated.
Copyright (C) 2025 Tally