Native SOL Staking with SPL Token Rewards on Solana
A Solana staking program built with Anchor that allows users to stake native SOL, accumulate reward points over time based on amount and duration, and claim SPL token rewards. Stake more, stake longer, earn more.
Staking mechanisms need to fairly reward users based on both how much they stake and how long they keep it staked. A simple balance check at claim time doesn't capture duration — someone who staked 10 SOL for a month should earn more than someone who staked 10 SOL for a minute. The reward calculation needs to be continuous and tamper-proof.
This program tracks reward points that accumulate proportionally to amount × time. Every time the vault state changes (stake, unstake, or claim), points are recalculated based on the current balance and elapsed time since the last update. When the user claims rewards, SPL tokens are minted proportional to their accumulated points, and the counter resets to zero.
Program ID: 5RqZov6TkQYw4ASzf84WPdMqSrjnc53ZfzysNHm9HDJh
┌──────────────────────────────────────────────────────────────┐
│ 1. Create Stake Account │
│ │
│ User ──── create_stake_account ────► Vault PDA │
│ one-time setup seeds: [staking,user]│
│ stores: bump, ts │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 2. Stake SOL │
│ │
│ User ──── SOL (lamports) ────► Vault PDA │
│ system transfer balance increases │
│ points recalculated │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 3. Accumulate Points │
│ │
│ points += vault_balance × (now - last_update) │
│ │
│ Recalculated on every stake, unstake, and claim action │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 4a. Claim Rewards │
│ │
│ Vault ──── points ────► Reward Mint ────► User ATA │
│ read points mint SPL tokens receive tokens │
│ reset to 0 proportional reward earned │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 4b. Unstake SOL │
│ │
│ Vault ──── SOL (lamports) ────► User │
│ withdraw points recalculated │
│ validates balance balance decreases │
└──────────────────────────────────────────────────────────────┘
- Native SOL staking — deposit and withdraw SOL directly into a program-controlled vault PDA
- Time-weighted rewards — points accumulate based on
balance × duration, rewarding both size and commitment - SPL token rewards — claim mints reward tokens proportional to accumulated points, then resets the counter
- Continuous accrual — points are recalculated on every state change (stake, unstake, claim), not on a fixed schedule
- Per-user vaults — each user gets their own vault PDA derived from
["staking", user] - One-time mint setup — a dedicated
create_mintinstruction initializes the reward token before any claims
| Instruction | Parameters | Description |
|---|---|---|
create_stake_account |
— | Initializes the user's vault PDA with the bump seed and current timestamp. Must be called before staking. Vault derived from ["staking", user]. |
stake |
amount: u64 |
Deposits native SOL (in lamports) into the vault. Recalculates accumulated points before transfer. |
unstake |
amount: u64 |
Withdraws SOL from the vault back to the user. Validates that the amount doesn't exceed the vault balance. Recalculates points before transfer. |
claim_rewards |
— | Mints SPL reward tokens to the user's associated token account based on accumulated points, then resets points to zero. |
create_mint |
— | Initializes the SPL reward token mint. Called once before any rewards can be claimed. |
| Field | Type | Description |
|---|---|---|
bump |
u8 |
PDA bump seed |
points |
u64 |
Accumulated reward points |
last_updated |
i64 |
Timestamp of last state change |
| Account | Seeds |
|---|---|
| Vault | ["staking", user.key()] |
staking/
├── src/
│ ├── lib.rs # Program entrypoint and instruction routing
│ ├── constants.rs # Reward rate constants
│ ├── errors.rs # Custom error types
│ ├── instructions/
│ │ ├── mod.rs # Module exports
│ │ ├── create_stake_account.rs # Vault initialization
│ │ ├── stake.rs # SOL deposit into vault
│ │ ├── unstake.rs # SOL withdrawal from vault
│ │ ├── claim_rewards.rs # Mint reward tokens
│ │ └── create_mint.rs # Initialize reward mint
│ └── state/
│ └── vault.rs # Vault account and point calculation
├── Cargo.toml
└── README.md
- Rust 1.75+
- Solana CLI 2.0+
- Anchor CLI 1.0+
git clone https://github.com/prranavv/Sol-Staking-Progra.git
cd Sol-Staking-Progra
anchor buildanchor deployanchor test| Component | Technology | Purpose |
|---|---|---|
| Runtime | Solana | High-throughput L1 blockchain |
| Framework | Anchor 1.0 | Solana program development framework with IDL generation |
| Token Standard | SPL Token | Reward token minting and distribution |
| Language | Rust | On-chain program logic |
"How are reward points calculated?"
Every time the vault state changes (stake, unstake, or claim), the program calculates
elapsed_time = now - last_updatedand addsvault_balance × elapsed_timeto the accumulated points. This means rewards scale linearly with both the amount staked and the duration.
"Why recalculate points on every action instead of at claim time?"
If points were only calculated at claim time, a user could stake 1 SOL for a month, then stake 1000 SOL one second before claiming, and get rewarded as if they had 1001 SOL staked the entire time. Recalculating on every state change snapshots the balance, preventing this manipulation.
"Why a separate create_mint instruction?"
The reward token mint only needs to be created once for the entire program. Separating it from the staking flow keeps the instructions focused and avoids paying mint creation rent on every new stake account.
"Why a separate create_stake_account instruction?"
The vault PDA needs to exist before any SOL can be deposited into it. Splitting account creation from staking makes the instructions composable — you create the account once and stake/unstake as many times as you want.
"What token do users receive as rewards?"
A custom SPL token whose mint is controlled by the program. The program is the mint authority, so only the
claim_rewardsinstruction can mint new tokens. The token has no intrinsic value — it represents the reward mechanism itself.
This staking program is a learning and portfolio project demonstrating Solana program development with the Anchor framework. It has not been audited and is not intended for production use with real funds. The reward token has no real-world value. Always conduct a thorough security audit before deploying any program that handles user assets.
MIT — see LICENSE for details.
Built by prranavv