Building My First Solana Smart Contract: A Simple Counter Explained

Building My First Solana Smart Contract: A Simple Counter Explained

Blockchain development is an exciting space, and Solana stands out for its high speed and low fees. As someone starting my journey in Solana smart contract programming, I wrote a very basic program to understand the fundamentals.

SolanaRustSmart Contract

Blockchain development is an exciting space, and Solana stands out for its high speed and low fees. As someone starting my journey in Solana smart contract programming, I wrote a very basic program to understand the fundamentals. This contract acts as a simple counter that can be increased or decreased by a specified value.

In this post, I will walk you through the entire process, explain the code line by line, and share important lessons I learned while building this contract. If you are new to Solana or smart contracts in general, this is a great place to start.


What is a Solana Smart Contract?

A Solana smart contract, also called a program, is a piece of code that runs on the Solana blockchain. It defines rules for how data on the blockchain can be read and modified. Unlike traditional software, it operates in a decentralized environment and interacts with accounts that hold state.


Why Build a Counter Contract?

Starting with a small project like a counter is helpful because it covers core concepts without overwhelming complexity. This contract will:

• Store a number on the blockchain (inside an account) • Accept instructions to increase or decrease the number • Update the stored number accordingly

This exercise introduces:

• How to define on-chain data structures • How to read and write account data safely • How to parse incoming instructions • How to structure your program's entrypoint


Tools and Libraries Used

Rust: The main language for Solana programs due to its safety and performance • Solana Program Library (solana-program crate): Provides essential APIs for writing on-chain programs • Borsh: A serialization format designed for speed and compactness, used for encoding and decoding data stored in accounts


Full Contract Code

Here is the complete code for the counter contract:

Rust
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
};
 
#[derive(BorshSerialize, BorshDeserialize, Debug)]
enum InstructionType {
    Increment(u32),
    Decrement(u32),
}
 
#[derive(BorshSerialize, BorshDeserialize, Debug)]
struct Counter {
    count: u32,
}
 
entrypoint!(counter_contract);
 
pub fn counter_contract(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let acc = next_account_info(&mut accounts.iter())?;
 
    let instruction_type = InstructionType::try_from_slice(instruction_data)?;
    let mut counter_data = Counter::try_from_slice(&acc.data.borrow())?;
 
    match instruction_type {
        InstructionType::Increment(value) => {
            counter_data.count += value;
        }
        InstructionType::Decrement(value) => {
            counter_data.count -= value;
        }
    }
 
    counter_data.serialize(&mut *acc.data.borrow_mut());
 
    msg!("Contract succeeded");
 
    Ok(())
}

Breaking Down the Code

1. Importing Necessary Modules

Rust
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
};

borsh: For serializing and deserializing data efficiently. • solana_program: Contains the building blocks for Solana programs, like account handling and entrypoints.

2. Defining Instructions

Rust
#[derive(BorshSerialize, BorshDeserialize, Debug)]
enum InstructionType {
    Increment(u32),
    Decrement(u32),
}

• The contract supports two instructions: increment and decrement. • Both carry a u32 value, indicating how much to add or subtract. • The InstructionType enum is serialized and deserialized with Borsh so it can be passed as raw bytes.

3. Defining On-Chain State

Rust
#[derive(BorshSerialize, BorshDeserialize, Debug)]
struct Counter {
    count: u32,
}

• This struct holds the actual count stored inside a Solana account. • The counter value is a 32-bit unsigned integer. • Like instructions, this struct uses Borsh for efficient encoding and decoding.

4. Declaring the Entrypoint

Rust
entrypoint!(counter_contract);

• This macro defines the function counter_contract as the program's entrypoint. • The Solana runtime calls this function whenever the program receives a transaction.

5. Implementing the Entrypoint Function

Rust
pub fn counter_contract(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {

program_id identifies the currently running program. • accounts is a slice of account references passed to the program. These contain data and lamports (Solana tokens). • instruction_data contains the raw bytes representing the instruction to execute.

6. Fetching the Account to Update

Rust
let acc = next_account_info(&mut accounts.iter())?;

• Retrieves the first account in the list. This is the account holding the counter data to be modified.

7. Decoding the Instruction Data

Rust
let instruction_type = InstructionType::try_from_slice(instruction_data)?;

• Uses Borsh to convert the raw bytes into an InstructionType. • This lets the contract know whether to increment or decrement and by how much.

8. Deserializing the Counter State

Rust
let mut counter_data = Counter::try_from_slice(&acc.data.borrow())?;

• Reads the account's current data and deserializes it into the Counter struct. • This allows the program to access and modify the current counter value.

9. Processing the Instruction

Rust
match instruction_type {
    InstructionType::Increment(value) => {
        counter_data.count += value;
    }
    InstructionType::Decrement(value) => {
        counter_data.count -= value;
    }
}

• Depending on the instruction, the contract either adds or subtracts the given value from the counter.

10. Serializing Updated State Back

Rust
counter_data.serialize(&mut *acc.data.borrow_mut());

• Writes the updated counter value back into the account's data storage. • This modifies the on-chain state to reflect the new count.

11. Logging Success and Returning

Rust
msg!("Contract succeeded");
 
Ok(())

• Prints a success message to the program log for debugging and monitoring. • Returns Ok(()) indicating the program executed without errors.


How Solana Stores Data

Solana stores program state inside accounts. Each account contains a byte array which can hold any serialized data. This contract uses Borsh to efficiently pack and unpack that data as Rust structs.


Lessons Learned

Writing this contract taught me several key things:

• How to define custom instructions and data structures that can be serialized to bytes. • How to handle account data safely and update on-chain state. • How the Solana entrypoint functions work, and how programs interact with accounts and instructions. • The importance of careful serialization/deserialization for blockchain programs. • Basic debugging by logging messages during execution.


Next Steps for Learners

If you want to take this further:

• Write tests for your contract using Solana's program testing framework. • Learn how to deploy your contract to Solana's Devnet or Testnet. • Build a simple frontend to interact with the contract via web3.js or Solana's client libraries. • Explore advanced Solana features like cross-program invocations, accounts with PDA (Program Derived Addresses), and error handling.


Final Words

Starting with simple projects like this counter contract is the best way to get comfortable with Solana's programming model. The ecosystem can seem complex at first, but by breaking problems down and practicing, you'll build a strong foundation for more advanced decentralized applications.

Try this code yourself, experiment with changes, and explore the vast possibilities Solana offers for blockchain developers.