Learn how staking works on Sui, including delegation, rewards, and the proof-of-stake consensus mechanism.
Staking on Sui allows SUI holders to participate in network security and earn rewards by delegating their tokens to validators. Sui uses a Delegated Proof-of-Stake (DPoS) consensus mechanism.
/// A staking pool embedded in each validator struct in the system state object.public struct StakingPool has key, store { id: UID, /// The epoch at which this pool became active activation_epoch: Option<u64>, /// The epoch at which this staking pool ceased to be active deactivation_epoch: Option<u64>, /// The total number of SUI tokens in this pool sui_balance: u64, /// The epoch stake rewards will be added here at the end of each epoch rewards_pool: Balance<SUI>, /// Total number of pool tokens issued by the pool pool_token_balance: u64, /// Exchange rate history of previous epochs exchange_rates: Table<u64, PoolTokenExchangeRate>, /// Pending stake amount for this epoch pending_stake: u64, /// Pending stake withdrawn during the current epoch pending_total_sui_withdraw: u64, /// Pending pool token withdrawn during the current epoch pending_pool_token_withdraw: u64, /// Any extra fields extra_fields: Bag,}
/// A self-custodial object holding the staked SUI tokens.public struct StakedSui has key, store { id: UID, /// ID of the staking pool we are staking with pool_id: ID, /// The epoch at which the stake becomes active stake_activation_epoch: u64, /// The staked SUI tokens principal: Balance<SUI>,}
StakedSui objects are NFTs that represent your stake. You retain custody and can transfer or trade them.
/// Request to stake to a staking pool. The stake starts counting at the beginning of the next epoch,public(package) fun request_add_stake( pool: &mut StakingPool, stake: Balance<SUI>, stake_activation_epoch: u64, ctx: &mut TxContext,): StakedSui { let sui_amount = stake.value(); assert!(!pool.is_inactive(), EDelegationToInactivePool); assert!(sui_amount > 0, EDelegationOfZeroSui); pool.pending_stake = pool.pending_stake + sui_amount; StakedSui { id: object::new(ctx), pool_id: object::id(pool), stake_activation_epoch, principal: stake, }}
Rewards are tracked through an exchange rate mechanism:
/// Struct representing the exchange rate of the stake pool token to SUI.public struct PoolTokenExchangeRate has copy, drop, store { sui_amount: u64, pool_token_amount: u64,}
When you stake:
You deposit SUI
Pool mints pool tokens at current exchange rate
Exchange rate improves as rewards are added
When you withdraw, pool tokens convert back to SUI at new rate
Epoch 0: Stake 100 SUI - Exchange rate: 1 pool token = 1 SUI - You get: 100 pool tokensEpoch 5: Rewards added (10 SUI) - Pool has: 110 SUI, 100 pool tokens - Exchange rate: 1 pool token = 1.1 SUI - Your tokens worth: 110 SUI (10 SUI reward)
/// Request to withdraw the given stake plus rewards from a staking pool.public(package) fun request_withdraw_stake( pool: &mut StakingPool, staked_sui: StakedSui, ctx: &TxContext,): Balance<SUI> { // stake is inactive and the pool is not preactive - allow direct withdraw if (staked_sui.stake_activation_epoch > ctx.epoch() && !pool.is_preactive()) { let principal = staked_sui.into_balance(); pool.pending_stake = pool.pending_stake - principal.value(); return principal }; let (pool_token_withdraw_amount, mut principal_withdraw) = pool.withdraw_from_principal( staked_sui, ); let principal_withdraw_amount = principal_withdraw.value(); let rewards_withdraw = pool.withdraw_rewards( principal_withdraw_amount, pool_token_withdraw_amount, ctx.epoch(), ); let total_sui_withdraw_amount = principal_withdraw_amount + rewards_withdraw.value(); pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + total_sui_withdraw_amount; pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + pool_token_withdraw_amount; principal_withdraw.join(rewards_withdraw); principal_withdraw}
After the warmup period, StakedSui can be converted to fungible form:
/// An alternative to `StakedSui` that holds the pool token amount instead of the SUI balance./// StakedSui objects can be converted to FungibleStakedSuis after the initial warmup period./// The advantage of this is that you can now merge multiple StakedSui objects from different/// activation epochs into a single FungibleStakedSui object.public struct FungibleStakedSui has key, store { id: UID, /// ID of the staking pool we are staking with pool_id: ID, /// The pool token amount value: u64,}