Solana Improvement Documents (SIMD) Reference
Solana Improvement Documents (SIMDs) are the formal mechanism for proposing changes to the Solana protocol. They cover everything from fee market restructuring to new syscalls, staking changes, and token standards. This skill covers the SIMDs that matter most for developers, along with the core Solana primitives (accounts, PDAs, CPIs, rent) that every program depends on.
What You Probably Got Wrong
AI models trained on Ethereum or outdated Solana docs produce broken code. Fix these assumptions first.
-
Solana programs are stateless — state lives in accounts, not contracts — There is no contract storage. Programs are executable code only. All mutable state is stored in separate data accounts that programs read and write via instruction inputs. If you write Solana code like Solidity (expecting
self.balances[user]), it will not compile. -
PDA bumps MUST be canonical —
Pubkey::find_program_addressreturns the canonical bump (highest valid bump 255..0). Always store and reuse this bump. Never callcreate_program_addresswith an arbitrary bump — it may produce a valid pubkey on the ed25519 curve, meaning it has a private key and is not a PDA. Anchor'sseedsconstraint handles this automatically. -
invoke_signedis NOT the same asinvoke—invokepasses through existing signers from the transaction.invoke_signedlets a PDA sign by providing the seeds + bump. Useinvokewhen a human wallet signs. Useinvoke_signedwhen your program's PDA needs to authorize a CPI call. -
Rent exemption is not optional in practice — Accounts below the rent-exempt minimum are garbage collected. Since SIMD-0084 (epoch-based rent collection removal), all new accounts must be rent-exempt at creation. The minimum is ~0.00089088 SOL per byte. Always calculate:
Rent::get()?.minimum_balance(data_len). -
Token-2022 is NOT a drop-in replacement for SPL Token — Programs that hardcode the SPL Token program ID (
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA) will reject Token-2022 tokens (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb). You must check both program IDs or use thespl_token_2022::check_spl_token_program_accounthelper. -
Priority fees are per compute unit, not per transaction —
ComputeBudgetProgram.setComputeUnitPrice({ microLamports })sets the price PER compute unit. Total priority fee =microLamports * compute_units_consumed / 1_000_000. Overpaying happens when you set highmicroLamportswithout reducing the compute unit limit. -
Account data size is fixed at creation — You cannot resize an account after creation (unless you use
realloc). Plan your data layout carefully.reallochas constraints: max 10KB increase per instruction, and the account must be owned by the calling program. -
CPI depth limit is 4, not unlimited — Program A calls B calls C calls D — that is depth 4, the maximum. Design your program architecture accordingly. Each CPI level also reduces available compute units.
How to Look Up Any SIMD
When a user asks about ANY SIMD — even ones not covered in this skill — fetch the full spec on demand.
Step 1: Find the filename
SIMD filenames include a slug (e.g., 0096-reward-collected-priority-fee-in-entirety.md), so you can't construct the URL from just the number. First, list the proposals directory and find the matching file:
WebFetch: https://api.github.com/repos/solana-foundation/solana-improvement-documents/contents/proposals
Prompt: "Find the filename that starts with {NNNN} (zero-padded to 4 digits)"
Examples: SIMD-96 → look for 0096-*, SIMD-33 → look for 0033-*
Step 2: Fetch the raw spec
Once you have the full filename:
WebFetch: https://raw.githubusercontent.com/solana-foundation/solana-improvement-documents/main/proposals/{full-filename}
Examples:
- SIMD-0096 →
https://raw.githubusercontent.com/solana-foundation/solana-improvement-documents/main/proposals/0096-reward-collected-priority-fee-in-entirety.md - SIMD-0033 →
https://raw.githubusercontent.com/solana-foundation/solana-improvement-documents/main/proposals/0033-timely-vote-credits.md
Step 3: Parse and summarize
The fetched markdown has YAML frontmatter (simd, title, authors, category, type, status, created) followed by sections: Summary, Motivation, New Terminology, Detailed Design, Impact, Security Considerations.
Extract and present: title, status, what it changes, implementation details, and impact on validators/developers.
Alternative methods
# GitHub CLI — list all proposals and grep for the number
gh api repos/solana-foundation/solana-improvement-documents/contents/proposals --jq '.[].name' | grep "^{NNNN}"
# Then fetch the matched file
gh api repos/solana-foundation/solana-improvement-documents/contents/proposals/{matched-filename} --jq '.content' | base64 -d
Sources
| Source | URL | Best for |
|---|---|---|
| GitHub repo | https://github.com/solana-foundation/solana-improvement-documents | Raw specs, PR discussions, git blame |
| Solana Forum | https://forum.solana.com/c/simd/5 | Community discussion, context, rationale |
- Raw specs:
https://raw.githubusercontent.com/solana-foundation/solana-improvement-documents/main/proposals/{filename} - All proposals: https://github.com/solana-foundation/solana-improvement-documents/tree/main/proposals
- Forum discussions: https://forum.solana.com/c/simd/5
SIMD Status Lifecycle
Draft -> Review -> Accepted -> Implemented -> Activated
-> Rejected
-> Withdrawn
- Draft: Initial proposal, open for feedback
- Review: Under formal review by core contributors
- Accepted: Approved for implementation
- Implemented: Code merged, awaiting feature gate activation
- Activated: Live on mainnet-beta
- Rejected/Withdrawn: Not proceeding
Core Solana Concepts
Accounts Model
Everything on Solana is an account. Programs, token mints, user wallets, PDAs — all accounts with different configurations.
| Field | Type | Description |
|---|---|---|
lamports |
u64 |
Balance in lamports (1 SOL = 1e9 lamports) |
data |
Vec<u8> |
Arbitrary byte array, max 10 MB |
owner |
Pubkey |
Program that owns this account (can write to data) |
executable |
bool |
Whether this account is a program |
rent_epoch |
u64 |
Deprecated since SIMD-0084 |
Key rules:
- Only the owner program can modify an account's
dataand debitlamports - Any program can credit lamports to any account
- Account data size is set at creation and requires
reallocto change - The System Program owns all wallet accounts (no data, just lamports)
Programs
Programs are stateless executable accounts. They process instructions that reference other accounts.
use anchor_lang::prelude::*;
declare_id!("YourProgramID11111111111111111111111111111");
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
let account = &mut ctx.accounts.my_account;
account.data = data;
account.authority = ctx.accounts.authority.key();
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + MyAccount::INIT_SPACE
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct MyAccount {
pub data: u64,
pub authority: Pubkey,
}
PDAs (Program Derived Addresses)
PDAs are deterministic addresses derived from seeds and a program ID. They are NOT on the ed25519 curve, so no private key exists — only the deriving program can sign for them.
Derivation
// On-chain: find canonical PDA
let (pda, bump) = Pubkey::find_program_address(
&[b"vault", user.key().as_ref()],
ctx.program_id,
);
// Verify PDA matches expected account
require_keys_eq!(pda, ctx.accounts.vault.key());
// Client-side: derive the same PDA
import { PublicKey } from "@solana/web3.js";
const [pda, bump] = PublicKey.findProgramAddressSync(
[Buffer.from("vault"), userPublicKey.toBuffer()],
programId
);
Anchor PDA Constraints
#[derive(Accounts)]
pub struct WithdrawFromVault<'info> {
#[account(
mut,
seeds = [b"vault", authority.key().as_ref()],
bump,
// Anchor derives the PDA, verifies it matches, stores the bump
)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>,
}
Canonical Bump Rule
// CORRECT: Use find_program_address (tries bumps 255 down to 0)
let (pda, bump) = Pubkey::find_program_address(&seeds, &program_id);
// WRONG: Never use create_program_address with a guess
// This may produce a valid ed25519 pubkey (has private key = NOT a PDA)
let pda = Pubkey::create_program_address(&[&seeds, &[arbitrary_bump]], &program_id);
CPIs (Cross-Program Invocations)
invoke — Pass-Through Signing
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Transfer, Token, TokenAccount};
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.source.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
invoke_signed — PDA Signing
pub fn transfer_from_vault(ctx: Context<VaultTransfer>, amount: u64) -> Result<()> {
let seeds = &[
b"vault",
ctx.accounts.authority.key.as_ref(),
&[ctx.accounts.vault.bump],
];
let signer_seeds = &[&seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.vault_token.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
};
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
token::transfer(cpi_ctx, amount)?;
Ok(())
}
Rent
Since SIMD-0084, all accounts must be rent-exempt at creation. The minimum balance is proportional to the account's data size.
// Calculate rent-exempt minimum
let rent = Rent::get()?;
let lamports = rent.minimum_balance(data_len);
| Data Size | Rent-Exempt Minimum |
|---|---|
| 0 bytes (wallet) | 0.00089088 SOL |
| 165 bytes (token account) | 0.00203928 SOL |
| 82 bytes (mint) | 0.00144768 SOL |
| 200 bytes | 0.00227616 SOL |
| 1,000 bytes | 0.00795168 SOL |
| 10,000 bytes | 0.07228128 SOL |
Reclaiming rent: close the account and transfer lamports back.
// Anchor: close an account and reclaim rent
#[derive(Accounts)]
pub struct CloseVault<'info> {
#[account(
mut,
close = authority,
seeds = [b"vault", authority.key().as_ref()],
bump,
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub authority: Signer<'info>,
}
Key SIMDs Deep-Dive
SIMD-0033 — Timely Vote Credits
Status: Activated
Validators earn more vote credits for voting quickly. A vote landing within 2 slots of the voted-on slot earns more credits than one landing 32 slots later. This incentivizes low-latency validator infrastructure and faster consensus convergence.
Developer impact: None directly. Validators with better infrastructure earn more staking rewards, affecting APY calculations for staking protocols.
SIMD-0046 — Versioned Transactions
Status: Activated
Introduced VersionedTransaction and Address Lookup Tables (ALTs). Legacy transactions are limited to 35 accounts (1232-byte size limit). V0 transactions reference ALTs to compress account lists, supporting 256+ accounts per transaction.
import {
Connection,
VersionedTransaction,
TransactionMessage,
AddressLookupTableProgram,
PublicKey,
} from "@solana/web3.js";
// Fetch an existing lookup table
const lookupTableAddress = new PublicKey("...");
const lookupTableAccount = await connection
.getAddressLookupTable(lookupTableAddress)
.then((res) => res.value);
if (!lookupTableAccount) throw new Error("Lookup table not found");
// Build a V0 transaction using the lookup table
const messageV0 = new TransactionMessage({
payerKey: payer.publicKey,
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
instructions,
}).compileToV0Message([lookupTableAccount]);
const tx = new VersionedTransaction(messageV0);
tx.sign([payer]);
const sig = await connection.sendTransaction(tx);
SIMD-0047 — Syscall Probing
Status: Accepted
Allows programs to query whether a syscall is available before calling it. Enables forward-compatible programs that can use new features when available and fall back gracefully when they are not. Important for programs deployed across multiple clusters at different feature activation stages.
SIMD-0096 — Reward Full Priority Fee to Validator
Status: Activated
100% of priority fees go to the block-producing validator (previously 50% was burned). This aligns validator incentives and reduces off-protocol fee arrangements. Developers still set priority fees the same way, but the economic distribution changed.
import { ComputeBudgetProgram } from "@solana/web3.js";
// Set compute unit limit (reduces wasted compute budget)
const setLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 200_000,
});
// Set priority fee in micro-lamports per compute unit
const setPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 50_000, // 50,000 micro-lamports per CU
});
// Total priority fee = 200,000 CU * 50,000 microLamports / 1,000,000 = 10,000 lamports
// = 0.00001 SOL
SIMD-0172 — Staking Rewards Distribution
Status: Accepted
Changes how staking rewards are distributed. Instead of bulk reward distribution at epoch boundaries (which causes slot-level congestion and delayed reward visibility), rewards are distributed incrementally within epochs. This smooths validator economics and reduces epoch-boundary performance impact.
Token Extensions (Token-2022)
Token-2022 (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) extends SPL Token with built-in features that previously required custom programs.
Key Extensions
| Extension | Description | Use Case |
|---|---|---|
| Transfer Fees | Percentage fee on every transfer | Protocol revenue, tax tokens |
| Confidential Transfers | Encrypted balances and amounts | Privacy-preserving payments |
| Interest-Bearing | Accumulating display balance | Yield-bearing stablecoins |
| Permanent Delegate | Authority that can transfer/burn any holder's tokens | Regulated assets, compliance |
| Non-Transferable | Soulbound tokens | Credentials, reputation |
| Transfer Hook | Custom program invoked on every transfer | Royalties, compliance checks |
| Metadata Pointer | On-chain metadata directly in mint | Eliminates Metaplex dependency |
| Group/Member | Hierarchical token relationships | Collections, token sets |
Creating a Token-2022 Mint with Transfer Fees
import {
Connection,
Keypair,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import {
TOKEN_2022_PROGRAM_ID,
createInitializeMintInstruction,
createInitializeTransferFeeConfigInstruction,
getMintLen,
ExtensionType,
} from "@solana/spl-token";
async function createMintWithTransferFee(
connection: Connection,
payer: Keypair,
mintAuthority: Keypair,
feeBasisPoints: number, // e.g., 100 = 1%
maxFee: bigint // max fee in token base units
): Promise<Keypair> {
const mintKeypair = Keypair.generate();
const extensions = [ExtensionType.TransferFeeConfig];
const mintLen = getMintLen(extensions);
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);
const tx = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: mintLen,
lamports,
programId: TOKEN_2022_PROGRAM_ID,
}),
createInitializeTransferFeeConfigInstruction(
mintKeypair.publicKey,
mintAuthority.publicKey, // transferFeeConfigAuthority
mintAuthority.publicKey, // withdrawWithheldAuthority
feeBasisPoints,
maxFee,
TOKEN_2022_PROGRAM_ID
),
createInitializeMintInstruction(
mintKeypair.publicKey,
9, // decimals
mintAuthority.publicKey,
null, // freezeAuthority
TOKEN_2022_PROGRAM_ID
)
);
await connection.sendTransaction(tx, [payer, mintKeypair]);
return mintKeypair;
}
Checking Token Program Compatibility
use anchor_lang::prelude::*;
// Accept both SPL Token and Token-2022
pub fn check_token_program(token_program: &AccountInfo) -> Result<()> {
let key = token_program.key();
require!(
key == spl_token::id() || key == spl_token_2022::id(),
ErrorCode::InvalidTokenProgram
);
Ok(())
}
Versioned Transactions and Address Lookup Tables
Creating an Address Lookup Table
import {
Connection,
Keypair,
AddressLookupTableProgram,
PublicKey,
} from "@solana/web3.js";
async function createLookupTable(
connection: Connection,
payer: Keypair,
addresses: PublicKey[]
): Promise<PublicKey> {
const slot = await connection.getSlot();
// Step 1: Create the lookup table
const [createIx, lookupTableAddress] =
AddressLookupTableProgram.createLookupTable({
authority: payer.publicKey,
payer: payer.publicKey,
recentSlot: slot,
});
// Step 2: Extend with addresses (max 30 per instruction)
const extendIx = AddressLookupTableProgram.extendLookupTable({
payer: payer.publicKey,
authority: payer.publicKey,
lookupTable: lookupTableAddress,
addresses,
});
// Send both in one transaction
const tx = new Transaction().add(createIx, extendIx);
await connection.sendTransaction(tx, [payer]);
return lookupTableAddress;
}
When to Use ALTs
- Transactions referencing more than ~20 unique accounts
- DeFi aggregators routing through multiple pools
- Batch operations touching many token accounts
- Any transaction hitting the 1232-byte size limit
Priority Fees in Practice
Estimating Priority Fees
import { Connection } from "@solana/web3.js";
async function estimatePriorityFee(
connection: Connection,
accountKeys: string[]
): Promise<number> {
const fees = await connection.getRecentPrioritizationFees({
lockedWritableAccounts: accountKeys.map(
(key) => new PublicKey(key)
),
});
if (fees.length === 0) return 0;
// Sort by fee, take the median for a balanced estimate
const sorted = fees
.map((f) => f.prioritizationFee)
.sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
return median;
}
Setting Priority Fees on a Transaction
import {
Connection,
Transaction,
ComputeBudgetProgram,
} from "@solana/web3.js";
function addPriorityFee(
tx: Transaction,
computeUnits: number,
microLamportsPerCU: number
): Transaction {
// Always set BOTH compute unit limit and price
tx.add(
ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: microLamportsPerCU })
);
return tx;
}
// Cost calculation:
// priority_fee_lamports = computeUnits * microLamportsPerCU / 1_000_000
// Example: 200,000 CU * 50,000 microLamports = 10,000 lamports = 0.00001 SOL
Priority Fee Tiers (March 2026 Estimates)
| Tier | microLamports/CU | Use Case |
|---|---|---|
| Low | 1,000 - 10,000 | Non-urgent transfers |
| Medium | 10,000 - 100,000 | Normal DeFi operations |
| High | 100,000 - 1,000,000 | Time-sensitive swaps |
| Urgent | 1,000,000+ | NFT mints, liquidations |
These vary significantly by network congestion. Always use getRecentPrioritizationFees for real-time estimates.
Key Constants
| Parameter | Value |
|---|---|
| Slot time | ~400ms |
| Epoch length | ~2 days (~432,000 slots) |
| Max transaction size | 1,232 bytes |
| Max accounts per tx (legacy) | ~35 |
| Max accounts per tx (v0 + ALT) | 256+ |
| Max CPI depth | 4 |
| Compute unit limit (default) | 200,000 per instruction |
| Compute unit limit (max) | 1,400,000 per transaction |
| Min rent per byte | ~0.00089 SOL |
| Max account data | 10 MB |
Max realloc increase per ix |
10,240 bytes (10 KB) |
| Lamports per SOL | 1,000,000,000 |
Network Endpoints
| Network | RPC | WebSocket |
|---|---|---|
| Mainnet | https://api.mainnet-beta.solana.com |
wss://api.mainnet-beta.solana.com |
| Devnet | https://api.devnet.solana.com |
wss://api.devnet.solana.com |
| Testnet | https://api.testnet.solana.com |
wss://api.testnet.solana.com |
Public endpoints are rate-limited. Use Helius, QuickNode, Triton, or Alchemy for production.
Quick SIMD Lookup
| SIMD | Title | Status |
|---|---|---|
| 0002 | Fee-payer signed first | Activated |
| 0033 | Timely Vote Credits | Activated |
| 0046 | Versioned Transactions | Activated |
| 0047 | Syscall Probing | Accepted |
| 0048 | Native Program Upgrades | Activated |
| 0072 | Priority Fee Market | Activated |
| 0083 | Token Extensions (Token-2022) | Activated |
| 0084 | Remove Rent Collection | Activated |
| 0096 | Reward Full Priority Fee to Validator | Activated |
| 0105 | QUIC Protocol for TPU | Activated |
| 0118 | Partitioned Epoch Rewards | Activated |
| 0133 | Increase Account Data Limit | Review |
| 0148 | Token Metadata in Token-2022 | Activated |
| 0159 | Reduce Rent Cost | Draft |
| 0163 | Multiple Delegations per Account | Review |
| 0172 | Staking Rewards Distribution | Accepted |
| 0175 | Confidential Transfers v2 | Review |
| 0185 | Vote Account Size Reduction | Draft |
| 0186 | Precompile for Secp256r1 | Activated |
| 0193 | ZK Token Proof Program | Review |
Full list: https://github.com/solana-foundation/solana-improvement-documents/tree/main/proposals
Related Skills
- solana-agent-kit — Solana Agent Kit for building AI agents that interact with Solana
- jupiter — Jupiter aggregator for token swaps and DCA
- drift — Drift Protocol for perpetuals and margin trading
References
- SIMD Repository: https://github.com/solana-foundation/solana-improvement-documents
- Solana Docs: https://docs.solana.com
- Anchor Framework: https://www.anchor-lang.com
- SPL Token Docs: https://spl.solana.com/token
- Token-2022 Docs: https://spl.solana.com/token-2022
- Solana Cookbook: https://solanacookbook.com
- Compute Budget: https://docs.solana.com/developing/programming-model/runtime#compute-budget
- Address Lookup Tables: https://docs.solana.com/developing/lookup-tables
Last verified: 2026-03-01