Nick Frostbutter

ArticlesCreate a Basic Anchor Program

Create a Basic Anchor Program

Nick FrostbutterNick Frostbutter
Jan 03, 2023

solanaanchor

The Anchor framework is the most popular rust framework for developing the on-chain Solana smart contracts called programs.

Get started with Anchor development for Solana

Install the Solana and Anchor CLIs

If you have not already setup your Solana development environment, you can view our other articles that go in depth on getting setup and all your tooling installed for Anchor and Solana program development.

  • Windows (coming soon!)
  • Mac OS (coming eventually?...)

Create a new Anchor project

From inside your terminal, you can create a new project using the Anchor CLI:

anchor init project_name

This creates a new directory named project_name in your current working directory, then scaffold out an Anchor workspace for you. The init command will also initialize a new git repo and NPM repo (using Yarn) for you. How sweet of them :)

You should then cd to your new project's root and you can get to work! PS: If you are using VS Code, be sure to open your new project in your editor's workspace.

Anchor project's structure

Your new Anchor project's has a few very important files and folder that were created during the init process.

You can change the names of some of these folders, but I wouldn't. Just keep them the same as the scaffold and move on :)

Important directories

  • app - the future home of your project's frontend code (aka the JavaScript)
  • programs - the home of all your project's Solana/Anchor programs (aka the rust code), but more on this later
  • tests - home for your JavaScript based end-to-end tests (using the Mocha testing framework)
  • target - home for your compiled Solana program and Anchor IDL file (more on this later too!)

Important files

  • Anchor.toml - manifest file containing config info and settings for your Anchor workspace
  • Cargo.toml - contains the Cargo package manager settings (same idea as package.json file for Node/NPM projects)
  • package.json - the standard JavaScript package file that will be used by your frontend

Build your Anchor program

Unlike vanilla rust based on-chain Solana program development, Anchor projects are built using the Anchor CLI. Which does the same BPF compiling that the native cargo-build-bpf command does, but it also adds in the Anchor magic of generating the IDL file (I promise, I will get to it. Just not yet...)

To build your Anchor project:

anchor build

Initial Anchor build

With every new project, I recommend running the build command right after your init because it may take some time for the rust compiler to compile all the assorted libraries used. So go get some coffee.

After the initial build is complete, go ahead and re-run the anchor build command. I'll wait....

It should be much faster. This is because the all the compiled rust code and packages do not all need to be recompiled from scratch. Each time you rebuild your project, effectively only the changed files will need to be recompiled. Making each subsequent build so much faster than the initial.

Key files generated from the build

The Anchor build command will not only compile your rust code into a deployable Solana program, but it generates a few other files too.

For simplicity, I will only cover the directly important files/folders generated from the build command.

Inside your target directory, the following important files are generated:

  • deploy/project_name.so - the Solana program binary ready to be deployed to the blockchain
  • deploy/project_name-keypair.json - a brand-new file system keypair file that will be used for your program
  • idl/project_name.json - a JSON file that will be used by your frontend to make interacting with your on chain program even easier
  • types/project_name.ts - auto generated typescript definitions that will be used by your frontend

Anchor.toml file

The Anchor.toml manifest file is where certain configuration settings for your Solana programs and Anchor workspace will live.

For simplicity, I will only focus on 2 main sections for this intro to Anchor:

  • [programs.cluster], and
  • [provider]

You can read more about the Anchor.toml file from the official Anchor lang docs.

Anchor.toml [programs.cluster]

The "programs" setting will allow you to save the string program_id of each of your Solana programs inside your project. Including being able to set a different program_id for each cluster, as desired.

For example, the following stores the program_id for the localnet cluster:

[programs.localnet]
demo = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"

To store a different program_id for different clusters, you can use something like this:

[programs.localnet]
demo = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"

[programs.testnet]
demo = "3ze6Z4rR8r8PkYAYYV5yZTRLKAHQMBcJj4AZMg4MS1fG"

Anchor.toml [provider]

You can specify a provider to store the desired Solana cluster and local keypair file (aka wallet) you want to have ownership of your deployed program.

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

Your actual Solana program code

Solana programs are different from regular rust based programs. Solana programs are created as rust "libraries" that are able to be loaded into the Solana blockchain's Sealevel runtime.

By convention, these libraries will typically have their primary code file of src/lib.rs inside of the programs/program_name directory of your Anchor workspace. (e.g. In the case of our example project, "project_name", this file will live at programs/project_name/src/lib.rs)

The default Anchor scaffold for lib.rs should look something like this:

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

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

This code, is essentially the most basic Solana Anchor program you can build. It seriously does nothing, except create itself. This code is the equivalent of return true; in most languages. Let's break down what the program structure and what is happening.

Import libraries

Like most other languages, it is also convention to "import" all of your desired items/libraries/crates at the top of your rust file. In rust, the use keyword is used to specify what crates to import into the specific rust file.

use anchor_lang::prelude::*;

For every Anchor based Solana program, the same basic import of the anchor_lang is used to tell the rust compiler to use the Anchor framework inside our code. Including setting it as part of the namespace so we can call the Anchor functions and magic sauce in the "local scope".

declare_id

Next, we are using the declare_id macro to statically define the program_id (aka public address) of our Solana program. This will enable some extra security in our program, courtesy of the Anchor framework.

This line of code should be in every Anchor program (specifically in lib.rs), and by convention just below your imports at the top of your file.

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

By default, the Anchor CLI (and anywhere else you get Anchor program examples) will have a program_id set with this macro.

You should ALWAYS change the value inside if your local source code to be the address of the keyfile being used to deploy your program (more on this in a bit).

The core logic for our Solana program

Next, we are creating a public module named program_name with the program attribute set on it.

No pun intended but, let's unpack that statement.

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

    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

Our public "module" will act as the container for the primary execution of code inside our program. Sort of like functions and methods do in other languages.

The program attribute is specially define by Anchor. Not only will handle lots of the boilerplate that is traditionally written in vanilla Solana programs, but it will also add some basic security check on our program.

So in a sense, you can think of defining our module with the program attribute like defining the main() function in other statically typed languages. Sort of.

use super::*;

Next we bring all the imported libraries from the program attribute into our local scope within our program_name module.

pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
  Ok(())
}

Finally we define a very simple initialize function that we can use to actually execute code on the Solana blockchain (via an RPC call).

For Anchor programs, every endpoint function will take one or more parameters. The first parameter will always be a Context of the desired struct defined in your Solana program. You can add more parameters, as needed for your program, to your functions.

You can read more about the Anchor context in the Anchor lang docs. But the gist is that this context will give us a very convenient way to read the accounts submitted to our functions.

We are also stating that our function will always return a Result type when it is complete (aka only 2 possible outcomes). This will allow our initialize function to return Ok(()) when it is successful, and Err() when an error occurs.

All Solana program functions are required to return a Result of some type. The Ok outcome is typically a blank value of (), while the Err() can be be any error you as the developer decide to return. Often times, developers will define a specific struct for creating standard/reusable errors. I strongly recommend doing the same, especially if you are going to open source your code. Think of others, be considerate!

Define our basic struct

Like I pointed out earlier, we need to define a struct used by our Context. This structure will tell Anchor the expected accounts that will be supplied to your function. And since the Solana runtime requires a complete listing of all accounts a program execution will interact with, Anchor can do some useful things.

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

In this specific example, our initialize function does not actually interact with any other Solana accounts, so our Initialize struct can remain effectively empty.

We are also using Anchor's derive(Accounts) macro to add some deserialization helpers onto our Initialize struct.

I will discuss these more in later tutorials when we really use them, but you can read up on this Accounts macro on the Anchor lang rust docs