Web3 & Blockchain/Solana/Deploy Program

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

ConceptEthereumSolana
LanguageSolidityRust (via Anchor)
StorageInside contractSeparate on-chain accounts
ExecutionEVMSealevel (parallel)
ProgramsStatefulStateless — state lives in accounts
UpgradesNeeds proxy patternNative (if upgrade authority set)
Deploy costGas (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 binary
  • target/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

Last updated · September 2026