QuickNode Raises $60M Series B!
Read the Letter from our CEO.

How to Write Your First Anchor Program in Solana: Part 2

December 06, 2022

Overview

Programs are what Solana calls Smart Contracts--they are the engine that handles information and requests: everything from token transfers and Candy Machine mints to 'hello world' logs and DeFi escrow governance. Solana supports writing on-chain programs using Rust, C, and C++ programming languages. Anchor is a framework that accelerates building secure Rust programs on Solana. Let's build your first Solana Program with Anchor!

What You Will Do

This is the 2nd guide in a 2 part series. In Part 1 we covered creating your first Solana Program on Anchor. The program allows a client-side request to be made to Solana's devnet and get a 'Hello World' log. In this guide, we will build on that Program and implement an increment function to track how many times your program has been used:

  • Create a program instruction to initialize a new data account
  • Update the hello_world function to update increment a data account on each call
  • Create a client-side request to call our updated function

What You Will Need

Initiate Your Project

We will be building off of the project from Part 1 of this Series. If you have that project open, go ahead and proceed to the next section.

If you do not already have the 'Hello World' project, you can create catch up by creating a new Anchor project on Solana Playground. Open lib.rs. Delete the existing contents, and paste this code:

initiate your project

Copy
use anchor_lang::prelude::*;

declare_id!("11111111111111111111111111111111");

#[program]
mod hello_world {
    use super::*;
    pub fn say_hello(_ctx: Context<SayHello>) -> Result<()> {
        msg!("Hello World!");       
        Ok(())
    }
}

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

Create and Connect a Wallet

Since this project is just for demonstration purposes, we can use a "throw-away" wallet. Solana Playground makes it easy to create one. You should see a red dot "Not connected" in the bottom left corner of the browser window. Click it:



Solana Playground will generate a wallet for you (or you can import your own). Feel free to save it for later use if you like, and click continue when you're ready. A new wallet will be initiated and connected to Solana devnet. Solana Playground airdrops some SOL to your new wallet automatically, but we will request a little extra to ensure we have enough for deploying our program. In the browser terminal, you can use Solana CLI commands. Enter solana airdrop 2 to drop 2 SOL into your wallet. Your wallet should now be connected to devnet with a balance of 6 SOL:



You are ready to go! Let's build!

Create a Counter Account

Create Initialize Structs

To count how many times our "hello_world" function has been called, we will need to create a new data account owned by our program. That data account will need two new structs:

1. an Account struct, Counter that will be used to keep track of our count (this will be an unsigned 64-bit integer, u64). #[account] pulls in some of the muscle of Anchor that does a lot of heavy lifting for us regarding serializing and deserializing our data.

create a counter account

Copy
#[account]
pub struct Counter {
    count: u64
}
2. a new Context will be used to create a new counter account. The context, Initialize, will tell our program which Public Keys we will need to provide when sending our transaction instructions from the client. #[derive(Accounts)] is abstracting a lot of content for us--we will cover that in a future guide. For now, know that Anchor is simplifying our code quite a bit!

create a counter account

Copy
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
Our Initialize struct will require three Public Keys:

  1. counter, the public key of the new data account we are creating (of type, Counter created above). Note here that we must pass a few arguments to initiate our new account:
    • init to tell Anchor that we are initializing a new account.
    • defining a public key responsible for paying the rent associated with the new account (payer), which we have set to our signer
    • space is used to calculate how many bytes our account will need (more bytes require more rent, so we want to be accurate with our requirements). We will need 8 bytes for our account discriminator (required for every account) and 8 bytes for our u64. See Anchor's Space Reference Guide for space requirements for all types.
  2. signer, the wallet responsible for signing the initialize transaction (and for paying the fees outlined above).
  3. system_program, which will own our new account and handle the SOL transfer between accounts

Create Initialize Counter Function

Great! Now that our structs are defined, we need to create an initialize function. Inside of hello_world after the say_hello function, create a new function, initialize_counter:

create initialize counter function

Copy
    pub fn initialize_counter(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count = 0;
        msg!("Initialized new count. Current value: {}!", counter.count);
        Ok(())
    }

Our function will pass a context, ctx, of type Initialize (from our previous step). Because we have included all of the necessary account information in our Initialize struct, Anchor can do most of the heavy lifting for us to create our new account. However, we need to call our counter account and set its initial value to 0. Because we are modifying counter, we must tag the variable as mutable using &mut. Finally, we will add a log show that our counter has been initiated with the correct initial balance. Anchor will make sure our data is serialized correctly!

Great job! Let's create our counter.

Initialize Counter from Client

First, we will need to Build and Update our program:

  • Click 🔧 Build on the left side of your screen to compile your code and check for errors. You should see a log like this in your console.
  • Click the Tools Icon 🛠 on the left side of the page, and then click "Upgrade." This will upgrade the program that we have already deployed to devnet.
  • Note: Solana Playground is still in beta. I had to refresh after deploying for my client-side Types to update with my new function. You may need to do this as well.

While the Program is deploying, head back to your client-side code, client.ts. You can delete the previous code and create a new call initializeCounter():

initialize counter from client

Copy
// 1 - Generate a new Keypair for the Counter Account
const counter = anchor.web3.Keypair.generate();
console.log('creating counter: ', counter.publicKey.toString());

// 2 - Fetch latest blockhash
let latestBlockhash = await pg.connection.getLatestBlockhash('finalized');

// 3 - Call initialize_counter and send the transaction to the network
const tx = await pg.program.methods
  .initializeCounter()
  // 3a - Pass the counter public key into our accounts context
  .accounts({counter: counter.publicKey})
  // 3b - Append the counter keypair's signature to transfer authority to our program
  .signers([counter])
  .rpc();

// 4 - Confirm the transaction
await pg.connection.confirmTransaction({
  signature: tx,         
  blockhash: latestBlockhash.blockhash,
  lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
});
console.log(`https://explorer.solana.com/tx/${tx}?cluster=devnet`); 

Let's break that down:

  1. Generate a new keypair that we will use for our new counter account.
  2. Fetch the latest blockhash.
  3. Like before, we will use Anchor to call our program's initialize_counter (note here in TypeScript, it is formatted initializeCounter). For this instance, we must pass the accounts we developed in our Initialize struct: counter, signer, and system program. Anchor knows we need to pass the system program and signer, so we do not need to add that here. Finally, since we want our Program to be able to update the data in this account in the future, we must have the counter assign authority to our program. We must append the account's signature to our method to do that.

Alright, let's make some magic. Click "▶️ Run" in the left sidebar to initialize your account. After the transaction processes, you should see a transaction URL in your terminal. Open it and scroll down to the transaction instructions. You should see something like this:



You can see that our Playground wallet created a new account that is assigned to our Program! You'll also notice that the account has been allocated 16 bytes of data. Finally, if you scroll down to our logs, you can see that our account state is 0, as expected. Nice job! Now we need to add our counter to our say_hello function, and we will be tracking devnet hello's forever!

Before moving on, copy your counter public key from your console and replace your client-side code with the public key:

initialize counter from client

Copy
const COUNTER = 'S61VvavzvHtGLEMxxavPdsKydRnwShxfbubGbw1mkPU'; //Replace with your public key

Implement Counter

We should now have a program that can say hello and a data account that can hold an integer value. We need to get those talking to each other. We need to update our say_hello function and the context we pass into, SayHello. Replace your say_hello function with:

implement counter

Copy
    pub fn say_hello(ctx: Context<SayHello>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count += 1;
        msg!("Hello World! - Greeting # {}", counter.count);
        Ok(())
    }

Our updated function first fetches the counter from the accounts object in our context. Since we are going to modify it, we must mark it as mutable, using &mut. We then increment our counter by 1 and log our new value with our greeting using msg!. We are almost ready, but first, we need to update our SayHello struct to pass our counter account. Update SayHello to include counter as a mutable account:

implement counter

Copy
#[derive(Accounts)]
pub struct SayHello<'info> {
    #[account(mut)]
    pub counter: Account<'info, Counter>,
}



Run it! 🏃‍♂️

Open client.ts. After your COUNTER address, add the following code:

run it

Copy
console.log(pg.wallet.publicKey.toString(), "saying hello:");

//1. Fetch latest blockhash
let latestBlockhash = await pg.connection.getLatestBlockhash('finalized');

//2. Call say_hello and send the transaction to the network
const counter = new anchor.web3.PublicKey(COUNTER);
 const tx = await pg.program.methods
  .sayHello()
  .accounts({counter})
  .rpc();

//3. Confirm the transaction and log the tx URL
await pg.connection.confirmTransaction({
  signature: tx,         
  blockhash: latestBlockhash.blockhash,
  lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
}); 
console.log(`https://explorer.solana.com/tx/${tx}?cluster=devnet`);  

//4. Query the chain for our data account and log its value
const data = await pg.program.account.counter.fetch(counter);
console.log('greeted', data.count.toNumber(),' times');

This should look very familiar to Part 1's call to sayHello, with a couple of small but significant differences:

  • In Step 2, we must now add the .accounts() method and pass our counter public key so that our program knows which counter to update (in theory, our program could handle any number of different counters).
  • Remember when we initialized counter, we needed to sign the transaction with the counter keypair? We do not need to do that here because we previously assigned the authority of the counter account to our program, meaning the program has the authority to change the data!
  • Finally, Step 4 is new. Now that we are writing data to the cluster, we must also read it. Anchor makes this easy by letting us call .fetch() on our data account. And then, we log our results.

Feel free to click "▶️ Run" a couple of times. You should see that your program is now tracking how many times it has said "Hello World":

Wrap Up

Great job with this! Your Program is starting to get a little more exciting! Understanding how to read and write data accounts through your programs, is really important concept that you will encounter frequently on your path as a Solana Developer.

Want to keep building? Check out our other Solana guides at quicknode.com/guides/solana-development/.

If you're stuck, have questions, or just want to talk about what you're building, drop us a line on Discord or Twitter!

We <3 Feedback!

If you have any feedback on this guide, let us know. We’d love to hear from you.

Additional Resources

Related articles 41

Solana NFT Metadata Deep Dive
Published: Dec 16, 2022
Updated: Dec 16, 2022

Even in the 2022 bear market, Solana NFTs are showing no signs of slowing down. If you are building with Solana NFTs, understanding your NFTs' metadata will make it easier for you to deploy...

Continue reading
How to Transfer SPL Tokens on Solana
Published: Sep 23, 2022
Updated: Sep 23, 2022

Sending Solana Program Library (SPL) Tokens is a critical mechanism for Solana development. Whether you are airdropping whitelist tokens to your community, bulk sending NFTs to another wallet,...

Continue reading
Como crear un NFT en SOLANA
Published: Dec 27, 2021
Updated: Sep 23, 2022

¡Hola querido lector! Bienvenidos a una nueva guía de Solana.Solana es una blockchain que promete mucho a la hora de intentar resolver los problemas de escalabilidad que...

Continue reading
How to Mint an NFT on Solana
Published: Aug 27, 2021
Updated: Sep 23, 2022

Updated at: April 10, 2022Welcome to another QuickNode guide on Solana - the up-and-coming blockchain that seeks to solve the scalability issues of Ethereum. We will be walking through...

Continue reading
How to Send Bulk Transactions on Solana
Published: Aug 31, 2022
Updated: Oct 3, 2022

Are you running a batch process that has many transactions? Perhaps an airdrop to your community's NFT holders or a token distribution to early users of your dApp. Solana transaction...

Continue reading
How to Burn SPL Tokens on Solana
Published: Jan 13, 2023
Updated: Jan 13, 2023

🔥🔥🔥Building a deflationary token protocol? Want to destroy a rugged NFT? Just want to have some fun with your community? The Solana SPL Token Program's Burn feature is what...

Continue reading
How to Get Transaction Logs on Solana
Published: Jun 24, 2022
Updated: Oct 27, 2022

Ever need to pull all the transactions associated with a Wallet? Want to see all of the mint transactions associated with a Candy Machine? Or maybe see transaction history of an NFT? Solana's...

Continue reading
How to Use Priority Fees on Solana
Published: Jan 13, 2023
Updated: Jan 17, 2023

Are you looking to get your transactions confirmed as quickly as possible on Solana? This guide will show you how to use priority fees to bid for priority in the leader's queue and confirm...

Continue reading
Solana Fundamentals Reference Guide
Published: Oct 27, 2022
Updated: Oct 27, 2022

The Solana blockchain is a powerful tool, delivering thousands of transactions per second with almost no-cost transaction fees. If you are new to Web3 or have developed on EVM-based...

Continue reading