A Guide to Counter DApp Development on Solana Blockchain

A Guide to Counter DApp Development on Solana Blockchain
6 min read

This blog gives a comprehensive guide to smart contract development using Anchor for counter dApp development on the Solana blockchain.

About Solana  

Solana blockchain development stands out for its high performance. Solana is known for its remarkable speed and scalability. It uses proof-of-history (PoH) and proof-of-stake (PoS) to process thousands of transactions per second, offering low fees. Solana's native cryptocurrency is SOL.

 It has a growing ecosystem that enables dApps (decentralized applications) to thrive. The network focuses on decentralization and interoperability. While it has faced some challenges, Solana remains a promising platform for blockchain innovation.

Writing Smart Contracts on Solana using Anchor 

Set Up Environment 

Install Rust, Solana CLI, and Anchor CLI for development, for more details refer to https://www.anchor-lang.com/docs/installation.

Initialize Project 

Use Anchor CLI to create a project with smart contract templates.

 Define Contract 

Write your smart contract logic in Rust within the lib.rs file, specifying state structures and initializing accounts.

 Compile and Deploy 

Use Anchor CLI to compile your code into a Solana program and deploy it to the network.

Interact 

Users can interact with your contract by sending transactions to its entry points.

Testing 

Test your contract and use Anchor for upgrades.

Creating an Update Counter Smart Contract 

About Smart Contract 

This is a simple smart contract built using Anchor. This smart contract allows you to initialize a counter account and then increment and decrement it. Only the creator can increment and decrement on the counter account they created and then choose to remove it.

Concepts Covered:

  1. PDA - Program Derived Address
  2. Structs
  3. Modify the state of the smart contract
  4. Add Signers for signing the transaction
  5. Error Code
  6. InitSpace for default spacing of account struct
  7. Closing a created PDA and getting the Rent SOL back

Dependencies Version 

Anchor 0.28.0

Solana CLI 1.16.9

Project Creation Command

anchor init solana-counter

Functions and Functionality 

First of all, we'll create an account struct that will store data.

 #[account]

#[derive(Default, InitSpace)]

pub struct Counter {

    pub owner: Pubkey,

    pub counter: u64,

    pub bump: u8,

}

Explanation

  1. Owner: To store the owner who created the counter account
  2. Counter: To store the count of a particular counter account
  3. Bump: To store the canonical bump of the account

Then we'll manage this created account as follows:

  1. Initialize: To initialize the counter account which can store the above details, below is the code to initialize a counter account. 

 

Account 

 

#[derive(Accounts)]

pub struct Initialize<'info> {

    #[account(

        init,

        payer = initializer,

        seeds = [

            COUNTER_SEED.as_bytes(),

            initializer.key.as_ref()

        ],

        bump,

        space = 8 + Counter::INIT_SPACE

      )]

    pub counter_account: Account<'info, Counter>,

    #[account(mut)]

    pub initializer: Signer<'info>,

    pub system_program: Program<'info, System>,

}

 

Function 

 

pub fn initialize(ctx: Context) -> Result<()> {

        let counter_account = &mut ctx.accounts.counter_account;

        counter_account.owner = ctx.accounts.initializer.key();

        counter_account.counter = 0;

        counter_account.bump = *ctx.bumps.get("counter_account").unwrap();

        Ok(())

}

Explanation 

 

  • Counter account: a PDA type account that will be initialized first with the seeds (COUNTER_SEED and initializer account) so that for every initializer there can be a different counter account, we'll store the owner, set the counter to 0 and the canonical bump.
  • Initializer: a Signer-type account that will pay for the account creation.
  • System Program: a System Program account that will be used to create an account with the System Program.

 

  1. Increment Counter: To increment the counter account, below is the code to increment a counter account. 

 

Account

 

#[derive(Accounts)]

pub struct Update<'info> {

    #[account(

        mut,

        seeds = [

            COUNTER_SEED.as_bytes(),

            updater.key.as_ref()

        ],

        bump = counter_account.bump,

      )]

    pub counter_account: Account<'info, Counter>,

    #[account(

        mut,

        constraint = updater.key() == counter_account.owner @ ErrorCode::AccessDenied

    )]

    pub updater: Signer<'info>,

}

 

Function

 

pub fn increment_counter(ctx: Context) -> Result<()> {

        let counter_account = &mut ctx.accounts.counter_account;

        counter_account.counter += 1;

        Ok(())

} 

Explanation 

  • Counter account: the same PDA account that we created before will be passed so that we can access the data from that account.
  • Updater: a Signer-type account that will pay for the account updation.

 

  1. Decrement Counter: To decrement the counter account, below is the code to decrement a counter account.

 

Function 

 

pub fn decrement_counter(ctx: Context) -> Result<()> {

        let counter_account = &mut ctx.accounts.counter_account;

        require!(counter_account.counter > 0, ErrorCode::InvalidCount);

        counter_account.counter -= 1;

        Ok(())

} 

Explanation 

  • Counter account: the same PDA account that we created before will be passed so that we can access the data from that account.
  • Updater: a Signer-type account that will pay for the account updation.

Note: We can use the same Update Account in Increment for decrement as well.

  1. Remove Counter: To remove the counter account, below is the code to remove a counter account.

Account  

#[derive(Accounts)]

pub struct Remove<'info> {

    #[account(

        mut,

        seeds = [

            COUNTER_SEED.as_bytes(),

            remover.key.as_ref()

        ],

        bump = counter_account.bump,

        close = remover

      )]

    pub counter_account: Account<'info, Counter>,

    #[account(

        mut,

        constraint = remover.key() == counter_account.owner @ ErrorCode::AccessDenied

    )]

    pub remover: Signer<'info>,

}

Function 

pub fn remove_counter(_ctx: Context) -> Result<()> {

        Ok(())

}

Explanation 

  • Counter account: the same PDA account that we created before will be passed so that we can access the data from that account.
  • Remover: a Signer type account, who will get the SOL for the account removal. 

Steps to Deploy on Localhost

"solana config get" - Make sure it shows localhost configuration, if not run the command below.

"solana config set -url localhost"

After the instance is set to localhost, run the command below.

"anchor localnet" - This will start a local validator so that you can deploy and run the smart contract.

"anchor build" - This will build the smart contract.

"anchor keys list" - Copy the program ID that is returned and paste it in lib.rs and Anchor.toml also check if the cluster is set to "localnet".

"anchor build" - This will build the smart contract again with the updated program ID.

"anchor deploy" - This will deploy the smart contract on the localhost.

"anchor run test" - This will run the smart contract test cases.

Complete code - https://github.com/siddharth-oodles/solana-counter

Solana dapp Development with Oodles

Oodles Blockchain offers a wide range of dApp development services for the Solana blockchain. We enable you to develop robust Solana dApps for fintech, non-fungible token marketplaces, gaming, and beyond. Connect with our Solana developers to discuss your project needs.

 

In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.
Arslan Siddiqui 2
Joined: 6 months ago
Comments (0)

    No comments yet

You must be logged in to comment.

Sign In / Sign Up