Deploy a Solana Program
Solana programs are the equivalent of Ethereum smart contracts — but they're written in Rust and compiled to BPF bytecode. Anchor is the standard framework that wraps the low-level Solana SDK with a cleaner API.
Ethereum vs Solana — Key Differences
| Concept | Ethereum | Solana |
|---|---|---|
| Language | Solidity | Rust (via Anchor) |
| Storage | Inside contract | Separate on-chain accounts |
| Execution | EVM | Sealevel (parallel) |
| Programs | Stateful | Stateless — state lives in accounts |
| Upgrades | Needs proxy pattern | Native (if upgrade authority set) |
| Deploy cost | Gas (ETH) | ~2 SOL rent deposit |
The biggest mental shift: programs don't store data. All state lives in separate accounts that programs read and write.
Install Toolchain
# 1. Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# 2. Install Solana CLI
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
# 3. Install Anchor
cargo install --git https://github.com/coral-xyz/anchor avm --force
avm install latest && avm use latest
# Verify
solana --version
anchor --version
Create a New Project
anchor init my-program
cd my-program
Project structure
programs/
my-program/
src/
lib.rs ← Your program logic
Anchor.toml ← Config (cluster, program ID, etc.)
tests/
my-program.ts ← TypeScript integration tests
app/ ← Frontend (optional)
Basic Program (Counter)
// programs/my-program/src/lib.rs
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWxTWqNWBE5M4HfS5e8C1xrLgcEa");
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = 0;
counter.authority = ctx.accounts.user.key();
msg!("Counter initialized: {}", counter.count);
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
msg!("Counter incremented to: {}", counter.count);
Ok(())
}
}
// Account data structure
#[account]
pub struct Counter {
pub count: u64, // 8 bytes
pub authority: Pubkey, // 32 bytes
}
// Validation contexts
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = user,
space = 8 + 8 + 32 // discriminator + count + authority
)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
Build
anchor build
This compiles the Rust program and generates:
target/deploy/my_program.so— compiled BPF binarytarget/idl/my_program.json— IDL (like ABI on Ethereum)target/types/my_program.ts— TypeScript types
Test Locally
# Start local validator
solana-test-validator
# In another terminal, run tests
anchor test --skip-local-validator
// tests/my-program.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MyProgram } from "../target/types/my_program";
import { assert } from "chai";
describe("my-program", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MyProgram as Program<MyProgram>;
it("Initializes counter", async () => {
const counter = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({
counter: counter.publicKey,
user: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counter])
.rpc();
const account = await program.account.counter.fetch(counter.publicKey);
assert.equal(account.count.toNumber(), 0);
});
it("Increments counter", async () => {
// ... increment and assert count === 1
});
});
Deploy to Devnet
1. Configure cluster
solana config set --url devnet
2. Create or use a keypair
# Generate new keypair (saves to ~/.config/solana/id.json)
solana-keygen new
# Check your address
solana address
# Fund with devnet SOL
solana airdrop 2
3. Set program ID
# Get the program keypair address
solana address -k target/deploy/my_program-keypair.json
Paste it into declare_id!() in lib.rs and in Anchor.toml:
[programs.devnet]
my_program = "YOUR_PROGRAM_ID"
[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"
4. Deploy
anchor deploy --provider.cluster devnet
Output:
Program Id: YOUR_PROGRAM_ID
Deploy success
Deploy to Mainnet
# Switch to mainnet
solana config set --url mainnet-beta
# Update Anchor.toml
# [provider]
# cluster = "mainnet"
anchor deploy --provider.cluster mainnet
Deploying on mainnet costs real SOL. A typical Anchor program costs 1–3 SOL for the rent deposit. This SOL is recoverable if you close the program later.
Estimate cost before deploying
# Check compiled binary size
ls -la target/deploy/my_program.so
# Cost formula: ~0.00000348 SOL per byte
# 200KB program ≈ 0.7 SOL in rent
Upgrade a Deployed Program
Programs on Solana are upgradeable by default (unless you freeze them).
anchor upgrade target/deploy/my_program.so --program-id YOUR_PROGRAM_ID
To make a program immutable (no more upgrades):
solana program set-upgrade-authority YOUR_PROGRAM_ID --final
Call the Program from TypeScript
import * as anchor from "@coral-xyz/anchor";
import { Connection, PublicKey, clusterApiUrl } from "@solana/web3.js";
import idl from "./idl/my_program.json";
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const wallet = window.solana; // Phantom wallet
const provider = new anchor.AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
anchor.setProvider(provider);
const programId = new PublicKey("YOUR_PROGRAM_ID");
const program = new anchor.Program(idl as anchor.Idl, provider);
// Initialize a new counter account
const counter = anchor.web3.Keypair.generate();
await program.methods
.initialize()
.accounts({
counter: counter.publicKey,
user: provider.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counter])
.rpc();
// Read the account
const data = await program.account.counter.fetch(counter.publicKey);
console.log("Count:", data.count.toNumber());
// Increment
await program.methods
.increment()
.accounts({
counter: counter.publicKey,
authority: provider.publicKey,
})
.rpc();
Program Derived Addresses (PDAs)
PDAs are deterministic addresses derived from seeds — no private key, owned by the program. Essential for storing per-user state.
#[derive(Accounts)]
#[instruction(user_seed: String)]
pub struct CreateUserProfile<'info> {
#[account(
init,
payer = user,
space = 8 + 256,
seeds = [b"profile", user.key().as_ref()],
bump
)]
pub profile: Account<'info, UserProfile>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
// Derive PDA on client side
const [profilePda] = PublicKey.findProgramAddressSync(
[Buffer.from("profile"), wallet.publicKey.toBuffer()],
programId
);
Related: Anchor Programs | Create SPL Token | Faucets & Testnets