← Back to App

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.

PDA Seeds

AccountSeeds
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

Admin

Creates 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

Admin

Transfers tokens from the authority's wallet into the reward vault. These tokens are distributed to stakers when they claim rewards.

toggle_pause

Admin

Toggles the pool's paused state. When paused, new stakes are blocked but users can still unstake and claim existing rewards.

stake

User

Stakes 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

User

Withdraws a specified amount of staked tokens back to the user's wallet. Automatically claims any pending rewards before unstaking.

claim_rewards

User

Claims 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
WARNING Deploying to mainnet uses real SOL. Make sure you have sufficient balance and have thoroughly tested on devnet first.

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.

Tip: You can also test the pause/resume flow from the Admin page. Pausing the pool blocks new stakes but still allows unstaking and claiming — verify this yourself!

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)

NetworkURLNotes
Devnethttps://api.devnet.solana.comFree, rate-limited. Good for testing.
Mainnethttps://api.mainnet-beta.solana.comFree, heavily rate-limited. Not recommended for production.
WARNING Public RPCs have aggressive rate limits (roughly 10-40 req/s). For production apps, use a dedicated RPC provider.

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.

Tip: You can keep devnet and mainnet deployments separate by using different program keypairs. The frontend connects to whichever is configured in 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.

Tip: Deposit enough rewards to cover expected staking yields. If the reward vault runs dry, users' unclaimed rewards are preserved and can be claimed once you deposit more.

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.