SOLSTAKE Docs
Solana SPL Token Staking Protocol
Overview
SOLSTAKE is a non-custodial SPL token staking protocol built on Solana using the Anchor framework. It allows any SPL token holder to stake their tokens and earn yield at a fixed APY set by the pool authority.
Fixed APY
APY is set at pool creation and can never be changed — fully transparent and predictable.
Non-Custodial
All funds are held in PDA-controlled vaults. The authority cannot withdraw user deposits.
Pausable
The authority can pause new stakes, but users can always unstake and claim — even when paused.
Any SPL Token
Works with any existing SPL token mint. Just provide the mint address when initializing.
Architecture
The protocol uses two on-chain account types and PDA-controlled token vaults.
StakePool
StakeAccount
PDA Seeds
| Account | Seeds |
|---|---|
| StakePool | "stake_pool" + mint.key() |
| StakeVault | "stake_vault" + pool.key() |
| RewardVault | "reward_vault" + pool.key() |
| StakeAccount | "stake_account" + pool.key() + owner.key() |
Instructions
The program exposes 6 instructions — 3 admin-only and 3 user-facing.
initialize_pool
AdminCreates a new staking pool for a given SPL token mint. Sets the immutable APY (in basis points) and initializes the stake and reward vaults as PDA-controlled token accounts.
deposit_rewards
AdminTransfers tokens from the authority's wallet into the reward vault. These tokens are distributed to stakers when they claim rewards.
toggle_pause
AdminToggles the pool's paused state. When paused, new stakes are blocked but users can still unstake and claim existing rewards.
stake
UserStakes a specified amount of tokens into the pool. Creates a StakeAccount if one doesn't exist for the user, or adds to the existing stake. Records the timestamp for reward calculation.
unstake
UserWithdraws a specified amount of staked tokens back to the user's wallet. Automatically claims any pending rewards before unstaking.
claim_rewards
UserClaims accumulated staking rewards based on the pool's APY and time elapsed since last stake. Rewards are transferred from the reward vault to the user's token account.
Deployment Guide
Requirements
- Rust — latest stable via
rustup - Solana CLI — v1.18+ (
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)") - Anchor CLI — v0.30+ (
cargo install --git https://github.com/coral-xyz/anchor avm && avm install latest && avm use latest) - Node.js — v18+ with yarn or npm
Build the Program
Install dependencies
yarn install
Build the Anchor program
anchor build
This compiles the program and generates the IDL and keypair in target/.
Get your program ID
solana address -k target/deploy/sol_stake-keypair.json
Update declare_id! in programs/sol-stake/src/lib.rs and Anchor.toml with this address.
Rebuild with the correct program ID
anchor build
Deploy to Devnet
# Configure CLI for devnet
solana config set --url devnet
# Airdrop some SOL for deployment fees
solana airdrop 2
# Deploy
anchor deploy --provider.cluster devnet
Deploy to Mainnet
# Configure CLI for mainnet
solana config set --url mainnet-beta
# Ensure your wallet has enough SOL for deployment (~3-5 SOL)
solana balance
# Deploy
anchor deploy --provider.cluster mainnet
Testing on Devnet
Follow this walkthrough to test the full staking flow on Solana devnet. No real tokens are needed — everything uses free devnet SOL and a test SPL token created by the built-in faucet.
Prerequisites
- The program is deployed to devnet (see Deployment Guide)
- The frontend is running locally (
cd app && yarn dev) - A Solana wallet browser extension (Phantom, Solflare, or Backpack) set to Devnet
Get Devnet SOL
You need a small amount of devnet SOL to pay for transaction fees. Use either method:
# Via Solana CLI
solana airdrop 2 YOUR_WALLET_ADDRESS --url devnet
# Or visit the web faucet
# https://faucet.solana.com
Full Demo Walkthrough
Connect your wallet
Open http://localhost:3000 and click Select Wallet in the navbar. Choose your wallet and approve the connection. The network badge should show "Devnet".
Create a test token & pool (Admin)
Navigate to /admin. Expand the Test Faucet section and click Create Test Token + Pool. This mints a brand-new SPL token, airdrops 1,000 tokens to your wallet, and initializes a staking pool at 12% APY — all in one transaction.
Deposit rewards (Admin)
Still on the Admin page, scroll to Deposit Rewards. Enter an amount (e.g., 100) and click Deposit. This funds the reward vault so stakers can claim yield.
Stake tokens (User)
Go back to the Dashboard (/). Enter an amount to stake (e.g., 50) and click Stake. Your tokens are transferred to the PDA-controlled stake vault.
Watch rewards accrue
The dashboard shows a live "Pending Rewards" counter. Wait a minute or two and you'll see rewards ticking up based on the pool's 12% APY.
Claim rewards
Click Claim Rewards to transfer your accrued rewards from the reward vault to your wallet. Your token balance increases by the claimed amount.
Unstake tokens
Enter an amount and click Unstake. Your staked tokens are returned to your wallet, and any remaining pending rewards are automatically claimed at the same time.
Frontend Configuration
The frontend reads its configuration from app/src/lib/constants.ts:
import { PublicKey } from "@solana/web3.js";
export const PROGRAM_ID = new PublicKey(
"BiM1okba6ognpbwnMnD6kQsPtQ6LeEt5P9oTMpgHqSDX" // ← your program ID
);
export const RPC_ENDPOINT = "https://api.devnet.solana.com"; // ← your RPC
// Auto-detected from RPC_ENDPOINT — shown in the navbar badge
export const NETWORK_LABEL = ...; // "Devnet" | "Mainnet" | "Testnet" | "Localnet"
export const TOKEN_DECIMALS = 9;
export const SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
To switch networks, update PROGRAM_ID to your deployed program address and RPC_ENDPOINT to the appropriate cluster URL.
Running the Frontend
cd app
yarn install
yarn dev
The app runs at http://localhost:3000 by default.
RPC Options
Solana RPC endpoints determine which cluster your app connects to and affect performance and rate limits.
Public Endpoints (Free)
| Network | URL | Notes |
|---|---|---|
| Devnet | https://api.devnet.solana.com | Free, rate-limited. Good for testing. |
| Mainnet | https://api.mainnet-beta.solana.com | Free, heavily rate-limited. Not recommended for production. |
Paid RPC Providers
Helius
Solana-native. Generous free tier, DAS API support, webhooks.
QuickNode
Multi-chain. Fast endpoints, add-on marketplace, websocket support.
Triton (RPC Pool)
Solana-focused. High throughput, global edge network.
Alchemy
Multi-chain. Enhanced APIs, debugging tools, generous free tier.
Switching to Mainnet
Follow these steps to move from devnet to mainnet-beta.
Update Anchor.toml
Change the cluster from devnet to mainnet-beta:
[provider]
cluster = "mainnet-beta"
wallet = "~/.config/solana/id.json"
Deploy to mainnet
anchor deploy --provider.cluster mainnet
Note the deployed program ID.
Update constants.ts
Set PROGRAM_ID to your mainnet program address and RPC_ENDPOINT to a mainnet RPC URL (preferably a paid provider).
export const PROGRAM_ID = new PublicKey("YOUR_MAINNET_PROGRAM_ID");
export const RPC_ENDPOINT = "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY";
Initialize the pool with your token
Go to the Admin panel and paste your existing token's mint address into the "Initialize Pool" section. Do not use the Test Faucet — that creates a throwaway test mint.
constants.ts.
Admin Guide
The admin panel (/admin) lets the pool authority manage the staking pool.
Initialize a Pool
On the admin page, paste your SPL token's mint address and set the APY in basis points
(e.g., 1200 = 12%). Click Launch Pool. The APY is immutable
after creation.
Test Faucet (Demo Only)
For testing purposes, expand the "Test Faucet" section. This creates a brand-new SPL token, airdrops 1,000 tokens to your wallet, and initializes a pool with that test mint. This is only useful for demos on devnet.
Deposit Rewards
The reward vault must be funded for users to claim rewards. Use the "Deposit Rewards" section to transfer tokens from your wallet into the reward vault. You can deposit additional rewards at any time.
Pause & Resume
Toggle the pool's paused state. When paused, new stakes are blocked. Users can always unstake and claim rewards regardless of pause state — the protocol never locks user funds.
Security Model
Immutable APY
Set once at pool creation. The authority cannot change the rate after initialization — what users see is what they get.
PDA-Controlled Vaults
Both stake and reward vaults are owned by program-derived addresses. The authority cannot withdraw user-deposited funds.
Always Unstakeable
Users can unstake and claim rewards even when the pool is paused. The pause only prevents new stakes.
Preserved Rewards
If the reward vault is underfunded, unclaimed rewards are tracked on-chain and can be claimed once the vault is replenished.
Trust Assumptions
- The pool authority is trusted to fund the reward vault. If they don't, rewards accrue but can't be claimed until funded.
- The authority can pause new stakes but cannot prevent withdrawals or claim user funds.
- The program is upgradeable by default (standard Anchor deploy). To make it immutable, use
solana program set-upgrade-authority <PROGRAM_ID> --final.