The Sablier Staking protocol distributes rewards to users over time who stake ERC20 tokens in staking pools. The protocol supports two types of staking:
- Direct ERC20 Token Staking: Users can directly stake ERC20 tokens.
- Lockup NFT Staking: Users can stake Sablier Lockup NFTs that are streaming the supported staking token.
The protocol implements a snapshot-based reward calculation mechanism.
| Symbol | Description |
|---|---|
| Event such as stake, claim rewards etc. | |
| Time elapsed between events | |
| Total amount staked globally | |
| Amount staked by user | |
| Global rewards per token | |
| User's rewards per token | |
| Total global rewards distributed | |
| Total rewards earned by a user |
The protocol uses a snapshot mechanism to calculate user rewards:
It tracks cumulative rewards distributed per token. The cumulative rewards per token at event
Where the rewards distributed in period
So, the total rewards distributed until
Of course, by the end of the reward period,
It tracks rewards earned by each user using the global rewards distributed per token.
Now, to calculate user rewards, we first calculate rewards per token distributed since the last time user snapshot was taken.
When a user performs an action at event
The user's total claimable rewards become:
- Users stake tokens directly to the contract
- Rewards earned on the amount staked
- Users stake stream NFTs into the contract
- Rewards earned on the total tokens in the stream (including both locked and unlocked)
- Prevents withdrawal from staked streams
- Handles stream cancellation events
- Scaling Factor: Amounts are scaled to 1e20 decimals for higher precision during divisions, preventing precision loss in reward calculations regardless of the token decimals.
- No Staking: No rewards are distributed when no tokens are staked
| Status | Description |
|---|---|
SCHEDULED |
When start time is in the future. |
ACTIVE |
When current timestamp is in between start time and end time. |
ENDED |
When end time is in the past. |
stateDiagram-v2
direction LR
NULL --> SCHEDULED : createPool(startTime > now)
NULL --> ACTIVE : createPool(startTime = now)
SCHEDULED --> ACTIVE : time
ACTIVE --> ENDED : time
ENDED --> SCHEDULED : configureNextRound(startTime > now)
ENDED --> ACTIVE : configureNextRound(startTime = now)
NULL:::grey
SCHEDULED:::lightYellow
ACTIVE:::lightGreen
ENDED:::lightRed
classDef grey fill:#b0b0b0,stroke:#333,stroke-width:2px;
classDef lightGreen fill:#98FB98;
classDef lightYellow fill:#ffff99;
classDef lightRed fill:#ff4e4e;
flowchart LR
subgraph Statuses
NULL((NULL)):::grey
ACTIVE((ACTIVE)):::green
SCHEDULED((SCHEDULED)):::yellow
ENDED((ENDED)):::red
end
subgraph Functions
CLAIM_REWARDS([claimRewards])
CONFIGURE([configureNextRound])
CREATE([createPool])
STAKE([stakeERC20Token / stakeLockupNFT])
UNSTAKE([unstakeERC20Token / unstakeLockupNFT])
end
ALL(( )):::black
AS(( )):::black
classDef black fill:#000000,stroke:#333,stroke-width:2px;
classDef green fill:#32cd32,stroke:#333,stroke-width:2px;
classDef grey fill:#b0b0b0,stroke:#333,stroke-width:2px;
classDef yellow fill:#ffff99,stroke:#333,stroke-width:2px;
classDef red fill:#ff4e4e,stroke:#333,stroke-width:2px;
ALL --> ACTIVE & SCHEDULED & ENDED
AS --> ACTIVE & SCHEDULED
CREATE --> NULL
CONFIGURE -- "Snapshot Rewards" --> ENDED
CLAIM_REWARDS -- "Snapshot Rewards" --> ALL
STAKE -- "Snapshot Rewards" --> AS
UNSTAKE -- "Snapshot Rewards" --> ALL