Published 12/4/2025 · 5 min read
Tags: solana , javascript , web3 , programs , smart-contracts
We’ve been using the System Program to transfer SOL. But Solana has hundreds of programs you can interact with. Let’s understand how they work.
Programs vs Smart Contracts
On Ethereum, “smart contracts” store both code AND state in the same place. On Solana, they’re separated:
Ethereum: Solana:
┌─────────────────┐ ┌─────────────────┐
│ Smart Contract │ │ Program │
│ ───────────── │ │ (Code only) │
│ Code │ └────────┬────────┘
│ + │ │
│ State │ ▼
└─────────────────┘ ┌─────────────────┐
│ Accounts │
│ (State only) │
└─────────────────┘
Programs are stateless executables. They contain logic but store nothing.
Accounts store data. Programs read from and write to accounts.
This separation is why Solana is fast - it can parallelize transactions that touch different accounts.
Built-in Programs
Solana has several native programs:
| Program | Address | Purpose |
|---|---|---|
| System Program | 11111111111111111111111111111111 | Create accounts, transfer SOL |
| Token Program | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA | SPL token operations |
| Associated Token | ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL | Derive token account addresses |
| Memo Program | MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr | Attach memos to transactions |
You’ve already used the System Program (SOL transfers) and Token Program (USDC transfers).
Anatomy of an Instruction
When you call a program, you send an “instruction”:
interface TransactionInstruction {
programId: PublicKey; // Which program to call
keys: AccountMeta[]; // Which accounts to use
data: Buffer; // What to do (encoded)
}
interface AccountMeta {
pubkey: PublicKey;
isSigner: boolean; // Must sign the transaction?
isWritable: boolean; // Will be modified?
}
Example - a SOL transfer instruction:
import { SystemProgram, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const transferInstruction = SystemProgram.transfer({
fromPubkey: senderPublicKey,
toPubkey: recipientPublicKey,
lamports: 0.1 * LAMPORTS_PER_SOL,
});
// Under the hood, this creates:
// {
// programId: SystemProgram.programId, // 1111...
// keys: [
// { pubkey: sender, isSigner: true, isWritable: true },
// { pubkey: recipient, isSigner: false, isWritable: true }
// ],
// data: <encoded transfer amount>
// }
Program Derived Addresses (PDAs)
Programs often need to own accounts. But programs can’t sign transactions. Solution: PDAs.
A PDA is an address derived from:
- Seeds (any bytes you choose)
- The program’s address
import { PublicKey } from "@solana/web3.js";
// Derive a PDA
const [pda, bump] = PublicKey.findProgramAddressSync(
[
Buffer.from("user-account"), // Seed 1: string
userPublicKey.toBuffer(), // Seed 2: user's key
],
programId // The program
);
// The PDA is deterministic - same inputs always give same address
// The bump is a number that makes the PDA valid
PDAs are used for:
- Associated Token Accounts (your USDC account)
- Program-owned data accounts
- Authority accounts that programs control
Finding Token Accounts
The Associated Token Program uses PDAs to derive token account addresses:
import { getAssociatedTokenAddress } from "@solana/spl-token";
// This derives a PDA under the hood
const usdcAccount = await getAssociatedTokenAddress(
USDC_MINT, // Which token
walletPublicKey // Whose account
);
// Same wallet + same mint = always same address
// No need to store it - just derive it when needed
Reading Program Accounts
Many programs store data in specific account formats. Here’s how to read a Token account:
import { getAccount } from "@solana/spl-token";
const tokenAccount = await getAccount(connection, tokenAccountAddress);
console.log("Mint:", tokenAccount.mint.toBase58());
console.log("Owner:", tokenAccount.owner.toBase58());
console.log("Amount:", tokenAccount.amount); // BigInt
For custom programs, you need to know how to decode their account data.
The Solana Programming Model
Think of it like a database with special rules:
┌─────────────────┐
Transaction ───────▶│ Runtime │
│ │
│ 1. Load accts │
│ 2. Run program │
│ 3. Save accts │
└─────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Account │ │ Account │ │ Account │
│ (RW) │ │ (RO) │ │ (RW) │
└──────────┘ └──────────┘ └──────────┘
The runtime:
- Loads accounts specified in the instruction
- Runs the program with those accounts
- Saves any modified accounts
This is why you specify isWritable - the runtime needs to know what might change.
Cross-Program Invocation (CPI)
Programs can call other programs:
Your Transaction
│
▼
┌──────────────┐
│ Your Program │
│ │──CPI──▶┌──────────────┐
│ │ │ Token Program │
│ │◀───────│ │
└──────────────┘ └──────────────┘
This is how complex DeFi protocols work - your program calls the Token Program to move tokens, calls an Oracle program for prices, etc.
Common Programs You’ll Use
SPL Token Program - Fungible tokens (USDC, etc.)
import { createTransferInstruction } from "@solana/spl-token";
Metaplex - NFTs
// Various Metaplex SDKs
Marinade - Liquid staking
// marinade-finance SDK
Jupiter - Token swaps
import { Jupiter } from "@jup-ag/core";
What You Don’t Need to Know (Yet)
For x402 and most web app development, you don’t need to:
- Write Solana programs (Rust/Anchor)
- Understand the BPF bytecode format
- Deploy your own programs
You just need to interact with existing programs, which is all JavaScript.
What You Learned
- Programs are stateless code, accounts hold state
- Instructions tell programs what to do with which accounts
- PDAs let programs own accounts deterministically
- Token accounts are just PDAs derived from wallet + mint
- Programs can call other programs (CPI)
Next Up
Let’s put this into practice - interacting with the Token Program to check balances and transfer tokens in our Svelte app.
Resources:
Related Articles
- Understanding requestAnimationFrame
A practical guide to browser animation timing. Learn what requestAnimationFrame actually does, why it beats setInterval, and how to use it properly.
- Lambda Expressions vs Anonymous Functions
When learning a functional programming style you will often come across the term Lambda Expressions or Lambda Functions. In simple terms they are just functions that can be used as data and therefore declared as a value. Let's explore a few examples.
- JavaScript typeof Number
Often you will need to check that you have a number before using it in your JavaScript, here's how.