Creating a Staking Smart Contract on Solana using Anchor

Creating a Staking Smart Contract on Solana using Anchor
4 min read

This article explores a concise yet comprehensive guide on creating a staking smart contract on Solana utilizing Anchor. Explore the vast potential of Smart contract development company by leveraging Anchor's capabilities to build robust staking mechanisms. 

What is a Smart Contract

A smart contract is a self-executing program that automatically enforces the rules and regulations of a contract when certain pre-defined conditions are met. Smart contracts are usually written in programming languages such as Solidity for Ethereum or Rust for Solana and are stored on a blockchain, making them immutable and tamper-proof.

Smart contracts are an integral part of decentralized applications (dApps) that operate on blockchain networks. Smart contracts are transparent and secure, and provide a trustless environment for parties to interact with each other.

What is a Staking Contract 

A staking contract is a type of smart contract that allows users to earn rewards by staking their tokens or cryptocurrencies in a particular blockchain network. By staking their tokens, users can participate in the consensus process and earn rewards in return. 

Staking contracts can also include various features such as slashing mechanisms, where a portion of a staker's tokens is taken away as a penalty for malicious behavior or failure to perform network duties. Staking contracts can also implement additional features, such as delegation, slashing, and governance, to improve the staking experience and incentivize participation.

Installation and Versions Used in Code 

  1. Rust - 1.69.0
  2. Solana - 1.15.2
  3. Anchor - 0.27.0 

You can follow the link to install all the dependencies.

Code

use anchor_lang::prelude::*;

use anchor_spl::token::{self, MintTo, Transfer};

use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

declare_id!("7MHr6ZPGTWZkRk6m52GfEWoMxSV7EoDjYyoXAYf3MBwS");

#[program]

pub mod solana_staking_blog {

    use super::*;

    pub fn initialize(ctx: Context, start_slot: u64, end_slot: u64) -> Result<()> {

        msg!("Instruction: Initialize");

        let pool_info = &mut ctx.accounts.pool_info;

        pool_info.admin = ctx.accounts.admin.key();

        pool_info.start_slot = start_slot;

        pool_info.end_slot = end_slot;

        pool_info.token = ctx.accounts.staking_token.key();

        Ok(())

    }

    pub fn stake(ctx: Context, amount: u64) -> Result<()> {

        msg!("Instruction: Stake");

        let user_info = &mut ctx.accounts.user_info;

        let clock = Clock::get()?;

        if user_info.amount > 0 {

            let reward = (clock.slot - user_info.deposit_slot) - user_info.reward_debt;

            let cpi_accounts = MintTo {

                mint: ctx.accounts.staking_token.to_account_info(),

                to: ctx.accounts.user_staking_wallet.to_account_info(),

                authority: ctx.accounts.admin.to_account_info(),

            };

            let cpi_program = ctx.accounts.token_program.to_account_info();

            let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

            token::mint_to(cpi_ctx, reward)?;

        }

        let cpi_accounts = Transfer {

            from: ctx.accounts.user_staking_wallet.to_account_info(),

            to: ctx.accounts.admin_staking_wallet.to_account_info(),

            authority: ctx.accounts.user.to_account_info(),

        };

        let cpi_program = ctx.accounts.token_program.to_account_info();

        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

        token::transfer(cpi_ctx, amount)?;

        user_info.amount += amount;

        user_info.deposit_slot = clock.slot;

        user_info.reward_debt = 0;

        Ok(())

    }

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

        msg!("Instruction: Unstake");

        let user_info = &mut ctx.accounts.user_info;

        let clock = Clock::get()?;

        let reward = (clock.slot - user_info.deposit_slot) - user_info.reward_debt;

        let cpi_accounts = MintTo {

            mint: ctx.accounts.staking_token.to_account_info(),

            to: ctx.accounts.user_staking_wallet.to_account_info(),

            authority: ctx.accounts.admin.to_account_info(),

        };

        let cpi_program = ctx.accounts.token_program.to_account_info();

        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

        token::mint_to(cpi_ctx, reward)?;

        let cpi_accounts = Transfer {

            from: ctx.accounts.admin_staking_wallet.to_account_info(),

            to: ctx.accounts.user_staking_wallet.to_account_info(),

            authority: ctx.accounts.admin.to_account_info(),

        };

        let cpi_program = ctx.accounts.token_program.to_account_info();

        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

        token::transfer(cpi_ctx, user_info.amount)?;

        user_info.amount = 0;

        user_info.deposit_slot = 0;

        user_info.reward_debt = 0;

        Ok(())

    }

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

        msg!("Instruction: Claim Reward");

        let user_info = &mut ctx.accounts.user_info;

        let clock = Clock::get()?;

        let reward = (clock.slot - user_info.deposit_slot) - user_info.reward_debt;

        let cpi_accounts = MintTo {

            mint: ctx.accounts.staking_token.to_account_info(),

            to: ctx.accounts.user_staking_wallet.to_account_info(),

            authority: ctx.accounts.admin.to_account_info(),

        };

        let cpi_program = ctx.accounts.token_program.to_account_info();

        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

        token::mint_to(cpi_ctx, reward)?;

        user_info.reward_debt += reward;

        Ok(())

    }

}

#[derive(Accounts)]

pub struct Initialize<'info> {

    #[account(mut)]

    pub admin: Signer<'info>,

    #[account(init, payer = admin, space = 8 + PoolInfo::LEN)]

    pub pool_info: Account<'info, PoolInfo>,

    #[account(mut)]

    pub staking_token: InterfaceAccount<'info, Mint>,

    #[account(mut)]

    pub admin_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    pub system_program: Program<'info, System>,

}

#[derive(Accounts)]

pub struct Stake<'info> {

    #[account(mut)]

    pub user: Signer<'info>,

    /// CHECK:

    #[account(mut)]

    pub admin: AccountInfo<'info>,

    #[account(init, payer = user, space = 8 + UserInfo::LEN)]

    pub user_info: Account<'info, UserInfo>,

    #[account(mut)]

    pub user_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]

    pub admin_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]

    pub staking_token: InterfaceAccount<'info, Mint>,

    pub token_program: Interface<'info, TokenInterface>,

    pub system_program: Program<'info, System>,

}

#[derive(Accounts)]

pub struct Unstake<'info> {

    /// CHECK:

    #[account(mut)]

    pub user: AccountInfo<'info>,

    /// CHECK:

    #[account(mut)]

    pub admin: AccountInfo<'info>,

    #[account(mut)]

    pub user_info: Account<'info, UserInfo>,

    #[account(mut)]

    pub user_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]

    pub admin_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]

    pub staking_token: InterfaceAccount<'info, Mint>,

    pub token_program: Interface<'info, TokenInterface>,

}

#[derive(Accounts)]

pub struct ClaimReward<'info> {

    /// CHECK:

    #[account(mut)]

    pub user: AccountInfo<'info>,

    /// CHECK:

    #[account(mut)]

    pub admin: AccountInfo<'info>,

    #[account(mut)]

    pub user_info: Account<'info, UserInfo>,

    #[account(mut)]

    pub user_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]

    pub admin_staking_wallet: InterfaceAccount<'info, TokenAccount>,

    #[account(mut)]

    pub staking_token: InterfaceAccount<'info, Mint>,

    pub token_program: Interface<'info, TokenInterface>,

}

#[account]

pub struct PoolInfo {

    pub admin: Pubkey,

    pub start_slot: u64,

    pub end_slot: u64,

    pub token: Pubkey,

}

#[account]

pub struct UserInfo {

    pub amount: u64,

    pub reward_debt: u64,

    pub deposit_slot: u64,

}

impl UserInfo {

    pub const LEN: usize = 8 + 8 + 8;

}

impl PoolInfo {

    pub const LEN: usize = 32 + 8 + 8 + 32;

}

Steps to Run the Test Case

  1. NPM/Yarn install dependencies ("npm i" or "yarn")
  2. Run "solana-test-validator" in the new terminal
  3. Run "anchor test --skip-local-validator"

Output

solana-staking-blog

   âœ” Initialize

   âœ” Stake

   âœ” Claim Reward

   âœ” Unstake

The complete code can be found here.

If you want more information about smart contract development or want to get started with a project, connect with our skilled blockchain developers.

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: 5 months ago
Comments (0)

    No comments yet

You must be logged in to comment.

Sign In / Sign Up