An In-Depth Look into Fixed-Rate and Flexible Rate Staking Contracts On Celo

An In-Depth Look into Fixed-Rate and Flexible Rate Staking Contracts On Celo https://celo.academy/uploads/default/optimized/2X/3/302dd311d6c7a3844c47927cf14dfa2a4ce19569_2_1024x576.png
none 4.0 1

Introduction

Staking is the locking up of cryptocurrency tokens as collateral to help secure a network or smart contract, or to achieve a specific result. By staking, users gain an incentive for doing this. In this article we will be creating two staking smart contracts deployed on the Celo blockchain.
This article will guide you through the following:

  • Fixed Rate Staking: Creating a fixed rate smart contract using solidity.

  • Flexible Rate Staking: Creating a flexible rate smart contract using solidity.

  • Deployment: How to deploy your Smart Contract on the Celo blockchain using remix.

By the end of this tutorial article series, you will have an understanding of how to create a staking smart contract.

Here is a link to the complete code on complete GitHub.

Prerequisites

You will need solid and prior knowledge of the following:

  • Solidity: Solidity is an object-oriented programming language for implementing smart contracts on various blockchain platforms.

  • Blockchain concepts like Celo: A good understanding of basic blockchain technologies like Celo and their useful tool for development will suffice to take you through this tutorial.

Requirements

To complete this article

Let’s Begin

To get started, you will need to head over to Remix Website, this is where we will be creating our staking contract.

Fixed-Rate Staking Smart Contract

A fixed-rate staking contract is a type of blockchain-based smart contract that allows users to stake their digital assets for a predetermined fixed rate of return. What that means is that if we stake an amount of crypto currency, we will get the same staking reward no matter what.

Creating the Contract

Let’s head over to remix click on contract and create a new file called FixedStaking.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;


import "@openzeppelin/contracts/token/ERC20/ERC20.sol";


contract FixedStaking is ERC20 {


    mapping(address => uint256) public staked;
    mapping(address => uint256) private stakedFromTS;
   
    constructor() ERC20("Fixed Staking Coin", "FX") {
        _mint(msg.sender,10000);
    }


}

We create a Fixed Staking contract which extends open zeppelin ERC20 class where we create a token called Fixed Staking Coin then we send the deployer of the token 10000 coins.

Staking

function stake(uint256 amount) external {
        require(amount > 0, "the amount is too low");
        require(balanceOf(msg.sender) >= amount, "balance is too low");
        _transfer(msg.sender, address(this), amount);
        if (staked[msg.sender] > 0) {
            claim();
        }
        stakedFromTS[msg.sender] = block.timestamp;
        staked[msg.sender] += amount;
    }

In the fixed staking contract, we define a function that accepts an amount as input and stakes it if it passes all the necessary checks. These checks include if the amount is greater than 0 and also if we have enough tokens in our wallet to be staked. We use the block.timestamp to get the time we staked our tokens.

Unstake

 function unstake(uint256 amount) external {
        require(amount > 0, "amount is <= 0");
        require(staked[msg.sender] >= amount, "amount is > staked");
        claim();
        staked[msg.sender] -= amount;
        _transfer(address(this), msg.sender, amount);
    }

We declare an unstake function that checks the amount the user has staked. If the amount of tokens the user wants to unstake is less or equal to the amount the user staked we will be allowed to unstake and transfer the tokens to the user.

Claim Unstaked Tokens


 function claim() public {
        require(staked[msg.sender] > 0, "staked is <= 0");
        uint256 secondsStaked = block.timestamp - stakedFromTS[msg.sender];
        uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;
        _mint(msg.sender,rewards);
        stakedFromTS[msg.sender] = block.timestamp;
    }

Lastly I am sure you notice there is a claim function in both the stake and Unstake function. This function calculates the amount a user has earned from staking and sends the token earned to the user. We use the block.timestamp to calculate the amount of token to be paid to the user. After defining this function in our contract we will have completed our fixed rate staking smart contract. Here is the full smart contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;


import "@openzeppelin/contracts/token/ERC20/ERC20.sol";


contract FixedStaking is ERC20 {


    mapping(address => uint256) public staked;
    mapping(address => uint256) private stakedFromTS;
   
    constructor() ERC20("Fixed Staking", "FIX") {
        _mint(msg.sender,1000);
    }
    
    function stake(uint256 amount) external {
        require(amount > 0, "the amount is too low");
        require(balanceOf(msg.sender) >= amount, "balance is too low");
        _transfer(msg.sender, address(this), amount);
        if (staked[msg.sender] > 0) {
            claim();
        }
        stakedFromTS[msg.sender] = block.timestamp;
        staked[msg.sender] += amount;
    }


    function unstake(uint256 amount) external {
        require(amount > 0, "amount is <= 0");
        require(staked[msg.sender] >= amount, "amount is > staked");
        claim();
        staked[msg.sender] -= amount;
        _transfer(address(this), msg.sender, amount);
    }


    function claim() public {
        require(staked[msg.sender] > 0, "staked is <= 0");
        uint256 secondsStaked = block.timestamp - stakedFromTS[msg.sender];
        uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;
        _mint(msg.sender,rewards);
        stakedFromTS[msg.sender] = block.timestamp;
    }


}

Flexible-Rate Staking Smart Contract

A flexible rate staking contract is a type of blockchain-based smart contract that allows users to stake their digital assets and earn variable or flexible rates of return. Sometimes we may want to change the percentage a user earns after staking depending on some certain factors like the amount the user staked, the amount of time the token was staked etc. In this example we will be increasing the amount of tokens the user earns by the duration they have staked.

Creating the Contract

In remix click on contract and create a new file called FlexibleStaking.sol. We will be creating a similar contract but the difference will be the claim function.

 ``
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;


import "@openzeppelin/contracts/token/ERC20/ERC20.sol";


contract FlexibleStaking is ERC20 {


    mapping(address => uint256) public staked;
    mapping(address => uint256) private stakedFromTS;
   
    constructor() ERC20("Flexible Staking", "FXX") {
        _mint(msg.sender,1000);
    }


    function stake(uint256 amount) external {
        require(amount > 0, "the amount is too low");
        require(balanceOf(msg.sender) >= amount, "balance is too low");
        _transfer(msg.sender, address(this), amount);
        if (staked[msg.sender] > 0) {
            claim();
        }
        stakedFromTS[msg.sender] = block.timestamp;
        staked[msg.sender] += amount;
    }


    function unstake(uint256 amount) external {
        require(amount > 0, "amount is <= 0");
        require(staked[msg.sender] >= amount, "amount is > staked");
        claim();
        staked[msg.sender] -= amount;
        _transfer(address(this), msg.sender, amount);
    }


}

Creating Claim Function

function claim() public {
        require(staked[msg.sender] > 0, "staked is <= 0");
        uint256 secondsStaked = block.timestamp - stakedFromTS[msg.sender];


        if (secondsStaked < 2.592e6) {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            uint256 reward =  rewards / 10;
            _mint(msg.sender,reward);
            // 10% rate for staking periods less than a month
        } else if (secondsStaked < 7.776e6) {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            uint256 reward =  rewards / 4;
            _mint(msg.sender,reward);
            // 25% rate for staking periods less than 3 months
        } else if (secondsStaked < 1.555e7) {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            uint256 reward =  rewards / 2;
            _mint(msg.sender,reward);
            // 50% rate for staking periods less than six months
        } else {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            _mint(msg.sender,rewards);
            // 100% rate for staking periods longer than six months
        }
       
        stakedFromTS[msg.sender] = block.timestamp;
    }

In our flexible rating smart contract we include a claim function that checks how many days a user has staked and uses it to calculate the staking reward. We calculate it in such a way that the tokens earned increases as the time staked increases and incentivizes long term staking. Here is the full smart contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;


import "@openzeppelin/contracts/token/ERC20/ERC20.sol";


contract FlexibleStaking is ERC20 {


    mapping(address => uint256) public staked;
    mapping(address => uint256) private stakedFromTS;
   
    constructor() ERC20("Flexible Staking", "FXX") {
        _mint(msg.sender,1000);
    }


    function stake(uint256 amount) external {
        require(amount > 0, "the amount is too low");
        require(balanceOf(msg.sender) >= amount, "balance is too low");
        _transfer(msg.sender, address(this), amount);
        if (staked[msg.sender] > 0) {
            claim();
        }
        stakedFromTS[msg.sender] = block.timestamp;
        staked[msg.sender] += amount;
    }


    function unstake(uint256 amount) external {
        require(amount > 0, "amount is <= 0");
        require(staked[msg.sender] >= amount, "amount is > staked");
        claim();
        staked[msg.sender] -= amount;
        _transfer(address(this), msg.sender, amount);
    }


    function claim() public {
        require(staked[msg.sender] > 0, "staked is <= 0");
        uint256 secondsStaked = block.timestamp - stakedFromTS[msg.sender];


        if (secondsStaked < 2.592e6) {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            uint256 reward =  rewards / 10;
            _mint(msg.sender,reward);
            // 10% rate for staking periods less than a month
        } else if (secondsStaked < 7.776e6) {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            uint256 reward =  rewards / 4;
            _mint(msg.sender,reward);
            // 25% rate for staking periods less than 3 months
        } else if (secondsStaked < 1.555e7) {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            uint256 reward =  rewards / 2;
            _mint(msg.sender,reward);
            // 50% rate for staking periods less than six months
        } else {
            uint256 rewards = staked[msg.sender] * secondsStaked / 3.154e7;


            _mint(msg.sender,rewards);
            // 100% rate for staking periods longer than six months
        }
       
        stakedFromTS[msg.sender] = block.timestamp;
    }


}

Deploying Smart Contract on Celo

To deploy our staking smart contract to the Celo blockchain we need to have added the Celo network to our Metamask wallet Here is an in depth guide (Alfajores Network). After doing this we fund our Celo wallet with Celo tokens, head over to remix click on deploy, select Injected provider as the environment and deploy. Here is a full tutorial on how to deploy on the Celo blockchain using remix.

Congratulations :sparkles:

That wraps up today’s topic on Fixed-Rate and Flexible Rate Staking Smart Contracts On Celo. Hopefully, you’ve learned a few things about deployment, staking on Celo that you can apply in the real world. You can check out some of my other articles, see you in the next one.

About the Author

Oselukwue Kinyichukwu is a Fullstack developer with a passion for learning, building, and teaching. You can follow me on Twitter, You can check out my profile on LinkedIn, and see what I’m building on GitHub.

9 Likes

Congratulations on your proposal being chosen as a standout this week at Celo Academy! As you prepare your tutorial, please ensure you follow our guidelines found here: Celo Sage Guidelines and Best Practices. Thank you for helping improve the experience of the developers learning at Celo Academy.

Note: Please focus entirely on the technical aspects of creating these types of contracts and avoid any terms or topics that may be perceived as financial advice.

6 Likes

I’m interested seeing how you’ll implement this in code. I’d like to learn from here.

5 Likes

Hi I’ll be reviewing this piece

3 Likes

Aright thank you.

4 Likes

Hi @kinyichukwu
this tutorial does look quite detailed and concise.
I’ll be moving to publish now

5 Likes

Thank you✨

2 Likes

:+1: @kinyichukwu
If I can help improve and optimize this code, I’d start with redundancy. Even though it is a mere tutorial, and not for production purposes, it’s always safe to add a clause for us to be sure the code if not production-safe.

6 Likes

Nice piece @kinyichukwu

4 Likes

Nice work

3 Likes

Nice piece @kinyichukwu
But use Alfajores Network instead.

4 Likes

Alright thank you @bobelr.

4 Likes

You’re welcome

6 Likes

Great work man! :+1:

4 Likes

I am glad you liked it. :sparkles::sparkles:

3 Likes

This is a great piece brother , well done @kinyichukwu

Thank you @Encrypted :heart:

2 Likes

Nice tutorial @kinyichukwu

3 Likes

Thanks for contributing this.

3 Likes

changed to GitHub