How to Deploy Your First Solana Smart Contract

Deploying a Solana smart contract to devnet using the Anchor framework

Deploying my first Solana program to devnet was the moment blockchain development clicked for me. Seeing a program go live on-chain — even on a test network — makes everything feel real. Since then I've deployed three production Anchor programs for Moonly, but the workflow starts the same way every time.

In this guide we'll create an Anchor project, understand the default program code, build it, deploy it to devnet, and verify it's live on Solana Explorer. By the end you'll have a working smart contract on-chain.

Prerequisites: You need Rust, Solana CLI, Anchor, and Node.js installed. If you haven't set these up yet, follow my Solana development setup guide first.

Initialize the Project

Create a new Anchor project:

anchor init hello-world

Navigate into the project directory. Here's what Anchor generated:

hello-world/
├── Anchor.toml          # Project configuration (cluster, program IDs, scripts)
├── Cargo.toml           # Rust workspace configuration
├── programs/
   └── hello-world/
       └── src/
           └── lib.rs   # Your program's entry point
├── tests/
   └── hello-world.ts   # TypeScript tests for your program
└── target/              # Build output (after anchor build)
    ├── deploy/
   └── hello_world-keypair.json  # Program's keypair (its on-chain address)
    ├── idl/
   └── hello_world.json          # Interface Definition Language for clients
    └── types/
        └── hello_world.ts            # TypeScript types generated from the IDL

The key files are lib.rs (your program code), the keypair (determines your program's on-chain address), and the IDL (a JSON description of your program's interface that clients use to interact with it).

Understanding the Default Program

Open programs/hello-world/src/lib.rs. Anchor generates a minimal program with one instruction:

use anchor_lang::prelude::*;

declare_id!("YOUR_PROGRAM_ID_HERE");

#[program]
pub mod hello_world {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Greetings from: {:?}", ctx.program_id);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

Let's break this down:

declare_id! — declares your program's on-chain address. This gets updated after the first build (more on that in a moment).

#[program] — marks the module containing your program's instructions. Each public function becomes an instruction that clients can call.

initialize — the instruction itself. It takes a Context<Initialize> which defines what accounts the instruction needs. Right now it just logs a message — we'll keep it simple for this first deployment.

#[derive(Accounts)] — the Initialize struct defines account validation. It's empty here because our instruction doesn't read or write any accounts. In a real program, this is where you'd specify the accounts your instruction needs and how they should be validated.

Build the Program

Compile the program:

anchor build

This compiles your Rust code to a .so file (the binary that runs on Solana), generates the IDL, creates TypeScript types, and — on the first build — generates a keypair for your program in target/deploy/hello_world-keypair.json.

Sync the Program ID

This is a step that trips up most beginners. The first anchor build generates a keypair, but your code still has a placeholder program ID. You need to sync them. Get your program's actual ID:

anchor keys list

This prints something like hello_world: 7nEBGke...your_program_id. Now update two places:

1. In programs/hello-world/src/lib.rs, replace the declare_id! value with your actual program ID.

2. In Anchor.toml, update the program ID under [programs.devnet] (or [programs.localnet]).

Then rebuild to embed the correct ID into the binary:

anchor build

If you skip this step, your program will deploy but clients won't be able to find it at the expected address. I learned this the hard way.

Configure for Devnet

Update Anchor.toml to target devnet — change the cluster field:

[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"

Also make sure your Solana CLI is pointing to devnet:

solana config set --url devnet

If you followed my setup guide, this should already be configured.

Get Devnet SOL

Deploying a program costs SOL (for storing the binary on-chain). Request some from the devnet faucet:

solana airdrop 2

Devnet airdrops are rate-limited to about 5 SOL every 2 hours. If the CLI airdrop fails, you can use the web faucet at faucet.solana.com — paste your wallet address and select devnet.

Check your balance with solana balance. You'll need roughly 2-3 SOL for a basic program deployment.

Deploy to Devnet

Everything is ready. Deploy:

anchor deploy

Anchor compiles your program (if needed), uploads the binary to devnet, and registers it at your program's address. As of Anchor v0.32.0, it also uploads the IDL automatically so clients can discover your program's interface on-chain.

You should see output confirming the deployment with your program ID. That's it — your program is live on devnet.

If you have multiple programs in the workspace, deploy a specific one with:

anchor deploy -p hello_world

Verify on Solana Explorer

Open Solana Explorer, switch to devnet, and search for your program ID:

https://explorer.solana.com/?cluster=devnet

Paste your program ID (from anchor keys list) into the search bar. You should see your program listed as an executable account with the deployment details — the binary size, the authority (your wallet), and the last deployment slot.

Seeing your program on Explorer is the best confirmation that everything worked. Bookmark this — you'll check it often during development.

Run the Tests

Your project came with a default test file at tests/hello-world.ts. Since we already deployed to devnet, run the tests against it:

anchor test --skip-local-validator

The --skip-local-validator flag tells Anchor not to start a local validator — instead it runs the tests against whatever cluster is configured in Anchor.toml (devnet in our case).

The default test calls the initialize instruction and confirms the transaction succeeds. Here's roughly what it looks like:

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { HelloWorld } from "../target/types/hello_world";

describe("hello-world", () => {
  anchor.setProvider(anchor.AnchorProvider.env());
  const program = anchor.workspace.helloWorld as Program<HelloWorld>;

  it("Is initialized!", async () => {
    const tx = await program.methods.initialize().rpc();
    console.log("Your transaction signature", tx);
  });
});

Notice the HelloWorld type import — this is generated from your IDL during anchor build. It gives you full TypeScript type safety when interacting with your program. Method names, account structs, and error codes are all typed.

Troubleshooting

Insufficient SOL: If deployment fails with an insufficient funds error, request more SOL with solana airdrop 2 or use the web faucet. A simple program needs about 2-3 SOL; complex programs with large binaries need more.

Buffer accounts: If a deployment fails mid-upload, it leaves behind buffer accounts that lock up your SOL. Reclaim them with:

solana program close --buffers

Program ID mismatch: If tests can't find your program, make sure you ran the Sync the Program ID step — the ID in declare_id!, Anchor.toml, and the keypair must all match.

Airdrop rate limit: If solana airdrop returns an error, you've hit the rate limit. Wait a couple hours or use faucet.solana.com as an alternative.

What's Next

You've deployed a Solana program to devnet — that's the foundation for everything that comes next. From here you can add accounts to store state, write more instructions to modify that state, and build a frontend that interacts with your program through the IDL.

The jump from "Hello World" to a real application is mostly about understanding Solana's account model — how accounts own data, how programs validate access, and how transactions batch instructions. That's where Anchor's account validation macros really start to shine.