Sol.College

Staking and reward accrual

Program-level staking usually means locking tokens or NFTs in a PDA-owned vault and tracking per-user state (amount, timestamps, reward debt) in a PDA keyed by the user. Rewards are often computed lazily from the Clock sysvar when the user interacts, instead of being pushed on a schedule. This is separate from native SOL stake accounts delegated to validators.

Reference: Solana docs: Staking · Anchor docs

Real code, line by line

3 excerpts from different projects. Each caption links the exact lines at the fork's pinned commit. The notes below each excerpt explain the lines that matter.

sevenlabs-hq/carbon · decoders/solayer-restaking-program-decoder/src/accounts/postgres/mod.rs L3–28 @ bebd108 · MIT · original
 pub use self::restaking_pool_row::*;use super::SolayerRestakingProgramAccount; pub struct SolayerRestakingProgramAccountsMigration; impl sqlx_migrator::Migration<sqlx::Postgres> for SolayerRestakingProgramAccountsMigration {    fn app(&self) -> &str {        "solayer-restaking-program"    }     fn name(&self) -> &str {        "solayer_restaking_program_accounts"    }     fn operations(&self) -> Vec<Box<dyn sqlx_migrator::Operation<sqlx::Postgres>>> {        vec![Box::new(RestakingPoolMigrationOperation)]    }     fn parents(&self) -> Vec<Box<dyn sqlx_migrator::Migration<sqlx::Postgres>>> {        vec![]    }} pub struct SolayerRestakingProgramAccountWithMetadata(    pub SolayerRestakingProgramAccount,
  1. Line 4 pub use self::restaking_pool_row::*;: Touches staking state: the amount locked and when it was locked.
anza-xyz/agave · runtime/src/stakes.rs L621–646 @ 5770743 · Apache-2.0 · original
            self.stake_delegations.remove(stake_pubkey);        }    }     fn add_delegated_stake(&mut self, voter_pubkey: Pubkey, stake: u64) {        if stake == 0 {            return;        }        *self.delegated_stakes.entry(voter_pubkey).or_default() += stake;    }     fn sub_delegated_stake(&mut self, voter_pubkey: &Pubkey, stake: u64) {        if stake == 0 {            return;        }        let current_stake = self            .delegated_stakes            .get_mut(voter_pubkey)            .expect("subtraction from missing delegated stake");        *current_stake = current_stake            .checked_sub(stake)            .expect("subtraction value exceeds delegated stake");        if *current_stake == 0 {            self.delegated_stakes.remove(voter_pubkey);        }    }
  1. Line 625 fn add_delegated_stake(&mut self, voter_pubkey: Pubkey, stake: u64) {: Touches staking state: the amount locked and when it was locked.
  2. Line 641 .checked_sub(stake): Uses checked arithmetic so overflow fails instead of wrapping.
otter-sec/anchor · spl/src/stake.rs L112–137 @ 541295d · Apache-2.0 · original
} #[derive(Accounts)]pub struct Withdraw<'info> {    /// The stake account to be updated    pub stake: AccountInfo<'info>,     /// The stake account's withdraw authority    pub withdrawer: AccountInfo<'info>,     /// Account to send withdrawn lamports to    pub to: AccountInfo<'info>,     /// Clock sysvar    pub clock: AccountInfo<'info>,     /// StakeHistory sysvar    pub stake_history: AccountInfo<'info>,} #[derive(Accounts)]pub struct DeactivateStake<'info> {    /// The stake account to be deactivated    pub stake: AccountInfo<'info>,     /// The stake account's stake authority
  1. Line 116 /// The stake account to be updated: Touches staking state: the amount locked and when it was locked.

Go further

Related lessons

Topics: DeFi · Learning path · Glossary