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
-
Remix: Remix Website
-
MetaMask Wallet Extension: You will need to have the MetaMask wallet extension installed in browser.
-
Fund Celo Wallet: Click on the link below and paste your wallet address to Fund Your TestNet Account (Alfajores Wallet).
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
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.