A full guide to building your first decentralized application (dApp) on the Solana blockchain using Rust and the Anchor framework.

Solana has captured the attention of the blockchain world with its promise of high-speed, low-cost transactions. For developers, this opens up a new opportunity for building decentralized applications (dApps) that require performance beyond what is possible on many other networks. While Solana development can be done in C and C++, the most popular and well-supported language is Rust, primarily through the
Anchor framework.
This guide will walk you through the core concepts and steps required to build your first dApp on Solana using Rust and Anchor. It is intended for developers who have some familiarity with programming concepts and a basic understanding of blockchain technology.
Before we start building, it's important to understand Solana's unique account model, which is different from account-based blockchains like Ethereum.
solana CLI, which is used to interact with the network, manage wallets, and more.avm (Anchor Version Manager). Follow the instructions on the Anchor website. The anchor CLI is your primary tool for creating, building, testing, and deploying your dApp.Let's build a classic "Hello, World!" of smart contracts: a simple counter program. This program will have two functions: one to initialize a counter at zero, and one to increment it.
Open your terminal and run:
anchor init my-counter-dapp
This command creates a new directory with the following structure:
programs/my-counter-dapp/src/lib.rs: This is where your on-chain Rust code (the program) will live.tests/my-counter-dapp.ts: This is a TypeScript file for writing tests for your program.Anchor.toml: This is the configuration file for your Anchor project.app/: A placeholder for a frontend application.lib.rsOpen programs/my-counter-dapp/src/lib.rs and replace the contents with the following:
use anchor_lang::prelude::*;
// This is your program's ID. Anchor will generate one for you when you build.
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod my_counter_dapp {
use super::*;
/ This function initializes our counter account
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter_account = &mut ctx.accounts.counter_account;
counter_account.count = 0;
msg!("Counter initialized at 0");
Ok(())
}
/ This function increments the count
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter_account = &mut ctx.accounts.counter_account;
counter_account.count += 1;
msg!("Counter incremented. Current count: {}", counter_account.count);
Ok(())
}
}
// This struct defines the accounts needed for the `initialize` function
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8)]
pub counter_account: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
// This struct defines the accounts needed for the `increment` function
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut)]
pub counter_account: Account<'info, Counter>,
}
// This struct defines the data structure of our counter account
#[account]
pub struct Counter {
pub count: u64,
}
Let's break this down:
#[program]: This attribute marks the my_counter_dapp module as the main module containing your program's instructions (functions).initialize and increment: These are the two instructions our program exposes. Notice how they both take a Context object as an argument.#[derive(Accounts)]: This is where the magic of Anchor happens. These structs define which accounts must be passed into an instruction. Anchor handles all the boilerplate of deserializing and validating these accounts for you.Initialize struct:init: This tells Anchor to create a new account.payer = user: The user account will pay the rent for the new account.space = 8 + 8: We must specify how much space to allocate for the new account. The first 8 bytes are a mandatory "discriminator" used by Anchor. The next 8 bytes are for our u64 count variable.#[account]: This attribute on the Counter struct tells Anchor that this struct defines the data layout for a specific type of account.Open tests/my-counter-dapp.ts and replace the contents:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MyCounterDapp } from "../target/types/my_counter_dapp";
import { expect } from "chai";
describe("my-counter-dapp", () => {
/ Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MyCounterDapp as Program<MyCounterDapp>;
/ Create a keypair for our counter account
const counterAccount = anchor.web3.Keypair.generate();
it("Is initialized!", async () => {
/ Call the initialize function
await program.methods
.initialize()
.accounts({
counterAccount: counterAccount.publicKey,
user: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([counterAccount])
.rpc();
/ Fetch the state of our counter account
const account = await program.account.counter.fetch(counterAccount.publicKey);
expect(account.count.toNumber()).to.equal(0);
});
it("Is incremented!", async () => {
/ Call the increment function
await program.methods
.increment()
.accounts({
counterAccount: counterAccount.publicKey,
})
.rpc();
/ Fetch the state again
const account = await program.account.counter.fetch(counterAccount.publicKey);
expect(account.count.toNumber()).to.equal(1);
});
});
This test uses the Anchor TypeScript client to interact with our program running on a local test validator. It first calls initialize and checks that the count is 0. Then it calls increment and checks that the count is 1.
Run the following commands in your terminal:
anchor build
This compiles your Rust program and generates the IDL, which is a JSON file that describes your program's interface. The Anchor client library uses this IDL to automatically generate a JavaScript/TypeScript client for your program.
anchor test
This will start a local Solana test validator, deploy your program to it, and run the TypeScript test suite. If all goes well, you should see both tests passing.
Explore more guides and career playbooks