November 9, 2022

Solana Smart Contract Examples for Developers

Table of Contents

Are you looking to get into Solana smart contract development? If so, you are exactly where you need to be, as this article introduces Solana development by exploring Solana smart contract examples. Exploring these is hugely beneficial as it gives us an overview of how Web3 contracts are structured on the Solana network. However, before diving into the examples, we’ll return to basics by studying the intricacies of smart contracts and their architecture. With that said, if you are already familiar with what Web3 contracts are and want to dissect the ones outlined in this article immodestly, feel free to jump to the “Solana Smart Contract Example” section!

Solana is a prominent programmable blockchain, and like many other networks, Solana features Web3 contracts. However, unlike other Ethereum alternatives, Solana is not EVM-compatible (incompatible with Ethereum Virtual Machine). As such, it means that Solana smart contract development differs compared to other EVM chains. 

For this reason, this article begins by diving deeper into the intricacies of Web3 contracts, followed by a section exploring the difference between Solana and EVM smart contracts. Once you have a more profound understanding of Solana smart contracts, the remaining parts outline a few examples to give you an idea of their structure.

Moralis

Additionally, if you are interested in Solana development, check out Moralis’ Solana API. This is one of Moralis’ selections of enterprise-grade Web3 APIs, making blockchain development significantly more accessible and Moralis an ideal “Web3 for business” alternative! Moreover, no matter what Web3 projects you are looking to create, sign up with Moralis to access a more seamless developer experience! 

What are Smart Contracts? 

Before diving into the Solana smart contract examples, we will go back to basics and explore the intricacies of smart contracts. If you are already familiar with the fundamental principles of smart contracts, feel free to jump straight to the ”Solana Smart Contract Examples” section. Otherwise, join us as we answer the question, ”what are smart contracts?”. 

Personel writing smart contracts inside their office.

Smart contracts (Web3 contracts) are programs hosted on a blockchain network executing predefined actions dependent on predefined conditions. Furthermore, Web3 developers use smart contracts to automate the execution of agreements between two or more parties. As such, Web3 contracts share the same fundamental function as traditional contracts, only that code mediates these digital programs instead of conventional intermediaries.

Smart contracts expand the basic notion behind Bitcoin, which is sending and receiving assets without intermediaries, by enabling the secure automation of any deal. Consequently, smart contracts make it possible to automate even more complex transactions/deals, and since they run on blockchain networks, they offer high reliability, security, and borderless accessibility.  

Furthermore, Web3 contracts are the backbone of the blockchain industry. These allow developers to create innovative dapps, tokens, and other Web3 projects. Also, smart contracts are utilized in everything from revolutionizing financial tools to game logic. Whenever a contract has been deployed on a blockchain network, they are generally irreversible or immutable, meaning that the contract cannot be altered. The immutability – in combination with the deterministic characteristic of smart contracts – ensures that participants can be certain of outcomes. 

Interestingly, smart contracts are sometimes called ”digital vending machines”, as vending machines are a good analogy for explaining the functionality of a smart contract. Like a conventional vending machine, smart contracts guarantee a particular output with the correct input. However, the transaction is often more complex than receiving a snack or soda. 

Does Solana Have Smart Contracts? 

Does Solana have smart contracts? The answer to this question is yes! Solana is a programmable decentralized blockchain enabling the creation of scalable, user-friendly dapps, and like all programmable blockchain networks, Solana features smart contracts. However, Solana smart contracts are different from, for example, EVM Web3 contracts. 

Solana.

Solana’s smart contract architecture slightly differs from the more conventional EVM-based blockchain models. For instance, Ethereum smart contracts have the code/logic and the state accumulated in only single contracts deployed on the Ethereum network. When it comes to Solana, smart contracts (or programs) are stateless or “read-only”, containing just the program logic. 

As soon as a contract deploys, it becomes possible to interact with them through external accounts. The accounts are then responsible for storing the data relating to the program interaction. Consequently, this creates a separation between the logic (programs) and the state (accounts). 

The distinction above outlines a crucial difference between Solana and other EVM-compatible blockchains in relation to smart contracts. Since there are differences in the smart contract architecture between EVM chains and Solana, there are also differences in how they are built. Developers use the Solidity programming language to write EVM-compatible smart contracts. Meanwhile, for Solana contracts, developers write using Rust, C, and C++.

As such, if you want to get into Solana smart contract development, it might be a good idea to become more proficient in the aforementioned programming languages. However, there are already many deployed programs/smart contracts on the Solana network for you to interact with. Accordingly, you only need to create new smart contracts occasionally when building on the Solana blockchain! 

Solana Smart Contract Examples

With a better understanding of smart contracts and what they entail in the context of Solana, the following section dives into some Solana sample smart contracts. This will provide an overview of what Solana smart contracts might look like, making the previous explanations more straightforward. 

A Solana smart contract example in the form of a digital paper.

Specifically, this section covers three Solana smart contract examples: 

  1. hello_world – The first sample smart contract is ”hello_world”, which is responsible for simply displaying a ”Hello World!!” message when someone calls the program. 
  2. tic_tac_toe – The second Solana sample smart contract is called ”tic_tac_toe”, which is a bit more complex since this contract is responsible for handling the game logic of a tic-tac-toe game.
  3. micro_blog – The final example we will examine further is called ”micro_blog”, which takes care of the necessary logic for a microblog. 

Nevertheless, let us jump straight into the first of our Solana smart contract examples and look closely at the ”hello_world” contract!

The ”hello_world” Contract

The first of our three Solana sample smart contracts, ”hello_world”, is relatively straightforward. You can find the entire code for this smart contract below:

use solana_program::{
    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
};

entrypoint!(hello_world);

pub fn hello_world(
    _program_id: &Pubkey, // Public key of the account the program was loaded into
    accounts: &[AccountInfo], // All accounts required to process the instruction
    _instruction_data: &[u8], // Serialized instruction-specific data
) -> ProgramResult {
    msg!("Hello {:}!!", accounts[0].key);
    Ok(())
}

Whenever someone calls this smart contract, it triggers a Solana transaction that the users need to sign. When they sign the message, it autonomously returns the contract’s data log, which, in this case, is a ”Hello World!!” message.

The ”tic_tac_toe” Contract

Next, let us take a closer look at ”tic-tac-toe”, the second sample smart contract. This contract is more complex than the previous one since it handled the logic for a multiplayer tic-tac-toe game. Nevertheless, this is the entirety of the Solana smart contract’s code:

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

pub fn win_check(moves: [u32; 9]) -> u32 {
    // Player 1 move will be marked as 1 and player 2 as 2
    let [m1, m2, m3, m4, m5, m6, m7, m8, m9] = moves;
    if (m1 == 1 && m2 == 1 && m3 == 1)
        || (m1 == 1 && m4 == 1 && m7 == 1)
        || (m7 == 1 && m8 == 1 && m9 == 1)
        || (m3 == 1 && m6 == 1 && m9 == 1)
        || (m1 == 1 && m5 == 1 && m9 == 1)
        || (m3 == 1 && m5 == 1 && m7 == 1)
        || (m2 == 1 && m5 == 1 && m8 == 1)
        || (m4 == 1 && m5 == 1 && m6 == 1)
    {
        // Condition for Player 1 Win
        return 1;
    } else if (m1 == 2 && m2 == 2 && m3 == 2)
        || (m1 == 2 && m4 == 2 && m7 == 2)
        || (m7 == 2 && m8 == 2 && m9 == 2)
        || (m3 == 2 && m6 == 2 && m9 == 2)
        || (m1 == 2 && m5 == 2 && m9 == 2)
        || (m3 == 2 && m5 == 2 && m7 == 2)
        || (m2 == 2 && m5 == 2 && m8 == 2)
        || (m4 == 2 && m5 == 2 && m6 == 2)
    {
        // Condition for Player 2 Win
        return 2;
    } else if (m1 == 1 || m1 == 2)
        && (m2 == 1 || m2 == 2)
        && (m3 == 1 || m3 == 2)
        && (m4 == 1 || m4 == 2)
        && (m5 == 1 || m5 == 2)
        && (m6 == 1 || m6 == 2)
        && (m7 == 1 || m7 == 2)
        && (m8 == 1 || m8 == 2)
        && (m9 == 1 || m9 == 2)
    {
        // Condition for Draw
        return 3;
    } else {
        return 0;
    }
}

#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct GameAccount {
    pub player1: String,
    pub player2: String,
    pub moves: [u32; 9],
    pub game_status: u32,
    pub next_move: u32,
}

entrypoint!(tic_tac_toe);

pub fn tic_tac_toe(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let game_account = &accounts[0];
    let player1 = accounts[1].key.to_string();
    let player2 = accounts[2].key.to_string();

    let instruction: u32 = instruction_data[0].into();
    let played_by: u32 = instruction_data[1].into();
    let move_positon: usize = instruction_data[2].into();

    match instruction {
        // Create New Game or Reset the Game Data
        0 => {
            msg!("Instruction 0 Start");
            let game_data = GameAccount {
                player1,
                player2,
                moves: [0, 0, 0, 0, 0, 0, 0, 0, 0],
                game_status: 0,
                next_move: 1,
            };
            msg!("Game Creation Successful!!");
            msg!("Player 1: {:?}", game_data.player1);
            msg!("Player 2: {:?}", game_data.player2);
            game_data.serialize(&mut &mut game_account.data.borrow_mut()[..])?;
            msg!("Instruction 0 End");
        }
        // Play game!!
        1 => {
            msg!("Instruction 1 Start");
            let mut game_data = GameAccount::try_from_slice(&game_account.data.borrow())?;
            if game_data.game_status == 0 {
                msg!("Player 1: {:?}", game_data.player1);
                msg!("Player 2: {:?}", game_data.player2);

                // Verify and updating moves in Game Account
                if (game_data.moves[move_positon] == 0) && (game_data.next_move == played_by) {
                    if game_data.next_move == 1 {
                        game_data.moves[move_positon] = 1;
                        game_data.next_move = 2
                    } else if game_data.next_move == 2 {
                        game_data.moves[move_positon] = 2;
                        game_data.next_move = 1
                    }
                } else {
                    msg!(" Wrong Move");
                }

                let game_status = win_check(game_data.moves);

                match game_status {
                    0 => {
                        // Log the next player to move
                        msg!("Next move: Player {}", game_data.next_move);
                    }
                    1 => {
                        game_data.game_status = 1;
                        msg!("Player 1 won the game.");
                    }
                    2 => {
                        game_data.game_status = 2;
                        msg!("Player 2 won the game.");
                    }
                    3 => {
                        game_data.game_status = 3;
                        msg!("It's a Draw.");
                    }
                    _ => {
                        msg!("Game Error!!");
                    }
                }
                // Write the updated data to account.
                game_data.serialize(&mut &mut game_account.data.borrow_mut()[..])?;
                msg!("Instruction 1 End");
            } else {
                msg!(" Wrong Move.");
            }
        }
        // Invalid Instruction
        _ => {
            msg!("Invalid Instruction");
        }
    }

    Ok(())
}

The code above is responsible for all of the tic-tac-toe’s game logic, which handles several aspects of the game. Initially, the contract checks if the two players already have a game currently on the way. If not, the smart contract creates a new game from scratch. Additionally, the contract checks if the right player is making a move and updates the state of the game accordingly. 

After each move, the contract calls the ”win_check()” function to check if either of the players has won the game. Finally, the game state returns to the users, enabling them to see updates to the gameboard in real time!

The ”micro_blog” Contract 

The final of our three initial Solana sample smart contracts is ”micro_blog”. Just like the first example, this is a relatively straightforward contract. Below, you will find the entirety of the code: 

use borsh::{BorshDeserialize, BorshSerialize};
use std::str;

use solana_program::{
    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg,
    program_error::ProgramError, pubkey::Pubkey,
};

// Create a struct to store Blog count
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct BlogCount {
    pub total_blogs: u32,
}

// Function to convert buffer array back to string
pub fn buffer_to_string(buffer: &[u8]) -> &str {
    let s = match str::from_utf8(buffer) {
        Ok(v) => v,
        Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
    };
    return s;
}

entrypoint!(micro_blog);

pub fn micro_blog(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let data = buffer_to_string(&instruction_data);

    let account = &accounts[0];

    // Check if the account is owned by this program, else throw an error.
    if account.owner != program_id {
        msg!(
            "Account {:?} does not have the program id {} as owner",
            account,
            program_id
        );
        return Err(ProgramError::IncorrectProgramId);
    }

    // Increment and store the number of times user created a new blog.
    let mut blog_counter = BlogCount::try_from_slice(&account.data.borrow())?;
    blog_counter.total_blogs += 1;
    blog_counter.serialize(&mut &mut account.data.borrow_mut()[..])?;

    // Save the data to the transaction logs
    msg!("Author: {}", accounts[1].key);
    msg!("Blog No: {}", blog_counter.total_blogs);
    msg!("Blog: {}", data);

    Ok(())
}

The purpose of this contract is to store blog data and track how many posts users publish. Consequently, the contract reads data from a frontend application, which are user inputs in the form of blog posts. Once a user issues a message, the contract increases the number that keeps track of how many posts have been published by that user. 

This covers the first three Solana sample smart contracts. However, we will explore the fourth example next, which is a bit special since it relates to NFTs. 

Solana NFT Smart Contract Examples

There is an abundance of examples we could outline herein. However, since we only have so much time on our hands, we’ll look at one carefully selected example. Now, before looking closer at our choice among several different Solana NFT smart contract examples, it is worth mentioning Metaplex. Metaplex is a prominent NFT ecosystem for games, marketplaces, arts, collectibles, etc. The protocol combines tools and smart contracts, enabling a seamless workflow for creating and launching NFTs. So, if you want to learn more about Solana NFT smart contract development, it is worth checking out Metaplex. 

Metaplex

Moreover, we bring up Metaplex because the Solana NFT smart contract we showcase below is based on the protocol. More specifically, we will briefly examine the Solana NFT smart contract for Metaplex’s Candy Machine. This is what the entirety of the code looks like: 

use anchor_lang::prelude::*;

pub use errors::CandyError;
use instructions::*;
pub use state::*;
pub use utils::*;

pub mod constants;
pub mod errors;
mod instructions;
mod state;
mod utils;

declare_id!("CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR");

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

    /// Add the configuration (name + uri) of each NFT to the account data.
    pub fn add_config_lines(
        ctx: Context<AddConfigLines>,
        index: u32,
        config_lines: Vec<ConfigLine>,
    ) -> Result<()> {
        instructions::add_config_lines(ctx, index, config_lines)
    }

    /// Initialize the candy machine account with the specified data.
    pub fn initialize(ctx: Context<Initialize>, data: CandyMachineData) -> Result<()> {
        instructions::initialize(ctx, data)
    }

    /// Mint an NFT. Only the candy machine mint authority is allowed to mint.
    pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, Mint<'info>>) -> Result<()> {
        instructions::mint(ctx)
    }

    /// Set a new authority of the candy machine.
    pub fn set_authority(ctx: Context<SetAuthority>, new_authority: Pubkey) -> Result<()> {
        instructions::set_authority(ctx, new_authority)
    }

    /// Set the collection mint for the candy machine.
    pub fn set_collection(ctx: Context<SetCollection>) -> Result<()> {
        instructions::set_collection(ctx)
    }

    /// Set a new mint authority of the candy machine.
    pub fn set_mint_authority(ctx: Context<SetMintAuthority>) -> Result<()> {
        instructions::set_mint_authority(ctx)
    }

    /// Update the candy machine configuration.
    pub fn update(ctx: Context<Update>, data: CandyMachineData) -> Result<()> {
        instructions::update(ctx, data)
    }

    /// Withdraw the rent lamports and send them to the authority address.
    pub fn withdraw(ctx: Context<Withdraw>) -> Result<()> {
        instructions::withdraw(ctx)
    }
}

The code above enables all the functionality for the NFT candy machine. Consequently, it takes care of all the logic for asset management, index generation/selection, and minting NFTs. Moreover, the contract makes it possible to mint individual NFTs or create them in bulk. 

That covers this tutorial’s Solana NFT smart contract example. The following section will quickly show you how to implement and deploy any of the Solana smart contract examples!

How to Deploy the Solana Smart Contract Examples

When you are done writing a contract, such as one of the Solana smart contract examples mentioned in this article, you need a way to build and deploy them to the Solana network. Consequently, this section outlines the steps in this process by showing you how to deploy the ”hello_world” contract, which was one of our Solana smart contract examples from one of the previous sections.

First up, if you have not already, set up Rust, the Solana CLI, and a Solana wallet. Next up, open an IDE of your choice and start a new terminal. From there, set up a ”Hello World” Cargo project by running the following command in the terminal: 

cargo init hello_world --lib

This will create a Cargo library in your directory with the files for building the Solana smart contract examples. You can then navigate to the ”hello_world” file with the command below:

cd hello_world

Next, open the ”Cargo.toml” file, copy the code snippet below, and add it at the bottom of the file: 

[lib]
name = "hello_world"
crate-type = ["cdylib", "lib"]

You can then navigate back to the terminal and add the Solana program package by running this command: 

cargo add solana_program

Finally, open the ”src/lib.rs” file and replace all of its contents with the ”hello_world” contract code from the ”Solana Smart Contract Examples” section:

use solana_program::{
    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
};

entrypoint!(hello_world);

pub fn hello_world(
    _program_id: &Pubkey, // Public key of the account the program was loaded into
    accounts: &[AccountInfo], // All accounts required to process the instruction
    _instruction_data: &[u8], // Serialized instruction-specific data
) -> ProgramResult {
    msg!("Hello {:}!!", accounts[0].key);
    Ok(())
}

With the contract code at your disposal, you should now be able to build the Solana smart contract by inputting the following Cargo command and running it in the terminal:

cargo build-bpf

From there, all that remains is to deploy the contract using the command below: 

solana program deploy ./target/deploy/hello_world.so

Now that’s it! You have now successfully created and deployed the ”hello_world” contract. You can now use the same principle for any other Solana smart contract examples you want to deploy! 

Summary – Solana Smart Contract Examples

If you followed along this far, you have now seen an outline of four different Solana smart contract examples. This article covered everything from a simple ”hello_world” smart contract displaying a ”Hello World!!” message to a more complex Solana NFT contract responsible for minting tokens. As such, we hope this provided insight into the structure of Solana smart contracts. Also, we hope it has inspired you to create your very own Solana smart contracts! 

If you found this guide helpful, check out some more content here at Moralis’ Web3 blog. The blog provides fresh and exciting Web3 development content for new and more experienced developers. For example, check out one of the recent guides on Dogechain or how to upload files to IPFS

Moreover, consider enrolling in Moralis Academy if you want to hone your Solana smart contract development skills. For example, check out the ”Rust Programming” course to become more prominent in Solana smart contract development! 

Moralis Academy Course: Solana Smart Contract Example Development

Additionally, if you want to build sophisticated Solana dapps, sign up with Moralis immediately. With the various Web3 APIs of Moralis, you can leverage the full power of blockchain technology to build dapps quicker!

Moralis Money
Stay ahead of the markets with real-time, on-chain data insights. Inform your trades with true market alpha!
Moralis Money
Related Articles
March 1, 2023

How to Build a Block Explorer

October 14, 2022

How to Write a Solana Smart Contract?

January 2, 2023

Get Contract Logs – Exploring Web3 Get Event Logs for Ethereum

November 16, 2022

Polygon Webhooks – How to Use Webhooks on Polygon 

March 16, 2023

Token Allowance Checker – View Wallet Token Approvals

September 27, 2022

Web3 Template – Build a Dapp Using a Web3 Website Template

September 28, 2022

How to Connect MetaMask to Website with NextJS

September 25, 2022

Full Guide: How to Build an Ethereum Dapp in 5 Steps

January 18, 2023

Supabase Authentication – How to Authenticate Users on Supabase