Epicentral LabsEpicentral Labs

The Staking Program

A technical overview of the Staking Program used for the Staking Lab and how it works.

Edit on GitHub

Instructions

The staking_program exposes eight main instructions defined in the program module:

InstructionDescription
initialize_stake_poolInitialize the stake pool and config accounts.
update_stake_poolUpdate the stake pool and config accounts.
delete_stake_poolDelete the stake pool and config accounts.
stake_to_stake_poolStake tokens to the stake pool.
unstake_from_stake_poolUnstake tokens from the stake pool.
claim_rewardsClaim rewards from the stake pool.
initialize_xlabs_mintInitialize the xLABS mint and metadata.
initialize_metadataInitialize the metadata for the xLABS mint.

Core Account Types

The program manages three primary account types:

StakePoolConfig

The global configuration account that stores pool parameters:

#[account(discriminator = 1)]
#[derive(InitSpace)]
pub struct StakePoolConfig {
    pub stake_mint: Pubkey,  // LABS address in this case
    pub reward_mint: Pubkey, // xLABS address in this case
    pub apr_bps: u128,       // Emission Rate in basis points (e.g., 10% is 1_000)
    pub bump: u8,
}

StakePool

The pool state account tracking the Global Interest Index and Vault:

#[account(discriminator = 2)]
#[derive(InitSpace)]
pub struct StakePool {
    pub vault: Pubkey, // the vault where the staked tokens are held
    pub config: Pubkey,
    // used to track the cumulative global interest for the pool,
    // to get the decimal of interest index, divide by 10^12
    pub interest_index: u128,
    pub interest_index_last_updated: i64, // timestamp of the last interest index update
    pub bump: u8,
}
  • Derived PDA: Uses seed ["stake_pool"]
  • Key Fields:
    • vault: Associated token account holding staked LABS tokens
    • interest_index: Cumulative interest index (scaled by 10^12 for precision)
    • interest_index_last_updated: Unix timestamp of last index update

StakeAccount

Per-user account tracking individual stake positions:

#[account(discriminator = 3)]
#[derive(InitSpace)]
pub struct StakeAccount {
    pub user: Pubkey,
    pub stake_pool: Pubkey,

    // We're using u64 here because that's the SPL token standard.
    pub staked_amount: u64,
    pub pending_rewards: u64,
    pub interest_index_at_deposit: u128,
    pub bump: u8,
}
  • Derived PDA: Uses seeds ["stake_account", stake_pool.key(), user.key()]
  • Key Fields:
    • staked_amount: Amount of LABS tokens currently staked (u64, SPL token standard)
    • pending_rewards: Accumulated but unclaimed xLABS rewards
    • interest_index_at_deposit: Baseline interest index for reward calculations

Access Control

Authority checks are enforced via the AUTHORITIES constant, which varies by network:

pub const AUTHORITIES: [Pubkey; 2] = [
    pubkey!("3BEvopNQ89zkM4r6ADva18i5fao1sqR1pmswyQyfj838"), // DAO Main Treasury
    pubkey!("5aVw8DnKuxjYRhvBDkE9Khh5NbxjUyu1pjDkgS6bdNFF"), // Core Team Hot Wallet
];

Only addresses in AUTHORITIES can:

  • initialize_stake_pool
  • update_stake_pool
  • delete_stake_pool
  • initialize_xlabs_mint
  • initialize_metadata

Security Features

  • Precise arithmetic: All calculations use PreciseNumber to prevent rounding errors
  • Overflow protection: All arithmetic operations use checked math
  • Account validation: Anchor constraints ensure account relationships via has_one checks
  • PDA derivation: Critical accounts use PDAs with deterministic seeds
  • Authority checks: Admin operations require signer to be in AUTHORITIES list
  • Vault safety: Vault must be empty before pool deletion

Last updated on