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.
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,- Line 4
pub use self::restaking_pool_row::*;: Touches staking state: the amount locked and when it was locked.
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); } }- Line 625
fn add_delegated_stake(&mut self, voter_pubkey: Pubkey, stake: u64) {: Touches staking state: the amount locked and when it was locked. - Line 641
.checked_sub(stake): Uses checked arithmetic so overflow fails instead of wrapping.
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- Line 116
/// The stake account to be updated: Touches staking state: the amount locked and when it was locked.
Go further
- carbon: explore the full project
- agave: explore the full project
- anchor: explore the full project
- Next lesson: NFTs with Metaplex
Related lessons
- Escrow patterns
- Program Derived Addresses (PDAs)
- Cross-Program Invocations (CPIs)
- SPL Tokens: mints, accounts and transfers
- Oracles: Pyth and Switchboard
Topics: DeFi · Learning path · Glossary