Hello! Welcome to the next phase of your Solana development journey. In the previous module, you built a program from scratch using native Rust, which gave you a fundamental understanding of how Solana works under the hood. You learned about manually deserializing instruction data, parsing account information, and writing data back to accounts.
This lesson marks our transition to the Anchor framework. We'll see how Anchor abstracts away much of the boilerplate and complexity you encountered, allowing you to focus more on your program's business logic. Your experience as a front-end developer, using frameworks to build applications, will be very relevant here. Think of Anchor as a powerful back-end framework for Solana, akin to something like Ruby on Rails or Express.js in the web world.
Today, we'll focus on the core structure of an Anchor program. Specifically, you will learn to define program instruction handlers and their context using Anchor macros. This is the first step in understanding how Anchor organizes and executes your program's logic.
From Native Solana to the Anchor Framework
Before we dive into Anchor's syntax, it's helpful to see what problems it solves. In the last module, you saw that a native Solana program has a single entry point that processes all instructions. This requires you to write a lot of manual code to:
- Parse instruction data to figure out which action to perform.
- Deserialize account data safely.
- Perform security checks, like verifying account ownership.
Anchor streamlines all of this. To get a clear picture of the benefits, let's start with a high-level overview.
Introduction to Anchor | Solana development
First, watch this short segment from Chainlink's "Introduction to Anchor" video. It explains what Anchor is and how it increases developer productivity by abstracting away low-level details, much like other development frameworks you may have used.
Watch the section from 00:16:09 to 00:17:51. Pay attention to the analogy with frameworks like Rails or Hardhat and the three main components Anchor provides: Rust macros, an Interface Definition Language (IDL), and a client library.
Now, to see a direct comparison between the native code you're familiar with and an Anchor program, watch this next clip.
Solana Smart Contract Tutorial: Using the Anchor Framework
This video from Josh's DevBox, "Solana Smart Contract Tutorial: Using the Anchor Framework", does an excellent side-by-side comparison of native Solana code versus its Anchor equivalent. This will make the value proposition of Anchor very concrete.
Watch the segment from 00:42:30 to 00:45:23. Notice how the single, large process_instruction function on the left is replaced by multiple, clearly defined functions in Anchor on the right. Also, note the reduction in boilerplate for serialization and security checks.
As you saw, Anchor fundamentally reorganizes the program structure from one monolithic entry point into a collection of distinct instruction handlers, making the code cleaner, safer, and easier to manage.
The #[program] Macro: Defining Your Instructions
In Anchor, the business logic of your program lives inside a Rust module annotated with the #[program] attribute. This macro tells Anchor that this module contains your instruction logic.
Each public function (pub fn) within this module becomes a distinct instruction that can be called by a client. This is a powerful convention: instead of a big match statement routing instructions, you simply define functions. This is conceptually similar to defining API endpoints in a web server.
The official Anchor documentation provides a clear and concise explanation of this structure. Please read the following sections from the "Program Structure" page.
First, read the introductory section that lists the main macros. Then, focus on the section titled "#[program] attribute". It explains how this attribute marks your instruction logic module. Take a look at the hello_anchor module in the example code to see this in practice.
Here's the key takeaway in code form, based on the documentation's example:
use anchor_lang::prelude::*;
declare_id!("11111111111111111111111111111111");
#[program]
mod hello_anchor {
use super::*;
// This is an instruction handler for the "initialize" instruction.
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
// Business logic goes here...
Ok(())
}
// You could add another instruction handler here.
// pub fn update_data(ctx: Context<Update>, new_data: u64) -> Result<()> { ... }
}
// ... (Account struct definitions would follow)
The #[program] macro wraps the hello_anchor module, and the initialize function within it is automatically exposed as an instruction. Any additional public functions you add would become other instructions for your program.
The Context Argument: Your Gateway to Accounts
You probably noticed that every instruction handler takes a special first argument: ctx: Context<T>. This Context is the cornerstone of Anchor's security and convenience. It acts as a container that provides your instruction with secure access to everything it needs to execute.
Instead of receiving a raw list of AccountInfo objects like in native Solana, Anchor gives you this structured Context object. It contains:
accounts: A deserialized and validated struct containing all the accounts your instruction needs. This is the most important field.program_id: The public key of the currently executing program.remaining_accounts: Any accounts passed to the instruction that were not defined in theaccountsstruct. Use with caution.bumps: Bump seeds used for Program Derived Addresses (PDAs), which we will cover in a future module.
The generic part, T in Context<T>, is a struct you define that lists the specific accounts required for that particular instruction. For example, Context<Initialize> tells Anchor to expect the accounts defined in the Initialize struct.
Let's reinforce this by watching another short video segment and reading the documentation.
Solana Smart Contract Tutorial: Using the Anchor Framework
This clip from Josh's DevBox explains the Context parameter and how it acts as a container for your account data.
Watch from 00:03:54 to 00:05:30. The key idea to grasp is that the Context holds the account data, and any parameters after the context are your custom instruction data.
Now, let's get the formal definition from the Anchor docs.
This section of the Anchor documentation provides a detailed breakdown of the Context struct and its fields.
Read the subsection titled "Instruction Context". It shows the definition of the Context struct and lists what its fields like accounts and program_id are used for. This will solidify your understanding of what ctx provides.
A Complete Example
Let's look at a complete, minimal program to see how these pieces fit together. This example defines a single instruction, hello, that simply logs a message.
use anchor_lang::prelude::*;
// 1. Declare the program's on-chain address (ID).
declare_id!("HZfVb1ohL1TejhZNkgFSKqGsyTznYtrwLV6GpA8BwV5Q");
// 2. Define the instruction module.
#[program]
mod hello_world {
use super::*;
// 3. Define the instruction handler.
// It takes a Context of type `Hello`.
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
msg!("Hello, World!");
Ok(())
}
}
// 4. Define the Accounts struct for the `hello` instruction.
// In this case, it's empty because the instruction doesn't need any accounts.
#[derive(Accounts)]
pub struct Hello {}
Based on the "Hello, World!" example from the Helius blog post "A Beginner's Guide to Building Solana Programs with Anchor".
Breaking it down:
declare_id!: Every program needs a unique address on the blockchain.#[program]: Marks thehello_worldmodule as our instruction container.pub fn hello(...): This is our instruction handler. Its name,hello, is the name of the instruction.Context<Hello>: It takes aContextgeneric over theHellostruct. This means Anchor will look for a struct namedHelloto understand which accounts to expect.#[derive(Accounts)] pub struct Hello {}: This defines the account context for thehelloinstruction. It's empty here, but in the next lesson, we will see how to populate this to work with on-chain data. The_in_ctxis a Rust convention to tell the compiler we know the variable is unused.
Test your understanding!
You are building a simple blogging program. You need to define two instructions: create_post and edit_post.
The create_post instruction will take one argument, a String for the blog post content. Its account context struct is named CreatePost.
The edit_post instruction will take two arguments: a u64 for the post ID and a String for the new content. Its account context struct is named EditPost.
Based on what you've learned, write the #[program] module containing the function signatures for these two instruction handlers. You don't need to implement the function bodies.
Show answer
use anchor_lang::prelude::*;
// Assume declare_id! and account structs are defined elsewhere
#[program]
mod blog_program {
use super::*;
pub fn create_post(ctx: Context<CreatePost>, content: String) -> Result<()> {
// Logic to create a post would go here
Ok(())
}
pub fn edit_post(ctx: Context<EditPost>, post_id: u64, new_content: String) -> Result<()> {
// Logic to edit a post would go here
Ok(())
}
}
// Account structs would be defined like this (details in the next lesson):
#[derive(Accounts)]
pub struct CreatePost<'info> { /* ... accounts for creating a post ... */ }
#[derive(Accounts)]
pub struct EditPost<'info> { /* ... accounts for editing a post ... */ }
This demonstrates the core pattern: one public function per instruction within the #[program] module, each taking a Context as its first argument, followed by any additional data required for the instruction.
Conclusion
In this lesson, you've taken the first crucial step into the Anchor framework. We've moved away from the low-level, manual processing of native Solana and embraced a more structured, declarative approach.
Here are the key takeaways:
- Anchor simplifies Solana development by abstracting away boilerplate code for serialization, deserialization, and common security checks.
- The
#[program]macro is used to define a module that contains all of your program's instruction handlers. - Each public function inside the
#[program]module corresponds to a single, callable instruction. - Every instruction handler's first argument must be
Context<T>, which provides secure access to accounts and other program information. The genericTspecifies the exact accounts required for that instruction.
You've now learned how to define the "endpoints" of your on-chain program. In the next lesson, we will focus on what goes inside Context<T>. You will learn how to define custom account structures using the #[account] attribute, which is how you tell Anchor what data to expect, how to (de)serialize it, and what security rules to enforce automatically.
Can't find a good explanation? Sign up and we'll make it for you
Sign up