Skip to main content

10 posts tagged with "intermediate"

View All Tags
Go back

· 11 min read
Mayowa Julius Ogungbola

Introduction

Non-fungible tokens (NFTs) have gained significant popularity in recent years due to their ability to represent unique digital assets such as artworks, collectibles, and even virtual real estate. One specific type of NFT, called ERC1155, allows for the creation and management of both fungible tokens within a single smart contract.

Minting your own NFTs on Celo is a relatively straightforward process. this tutorial will provide the necessary steps to successfully mint your ERC115 NFT on the Celo Blockchain network, with tips and best practices for successful minting, using the Remix IDE.

Additionally, this tutorial will provide a summary of the advantages of using the Celo blockchain to mint ERC1155 NFTs. And helps you demystify the process and provides the tools and resources necessary for successful NFT minting.

Prerequisites

Remix: You should be familiar with working on the Remix IDE. This tutorial will make use of the Remix IDE to create, deploy and mint your ERC token on the Celo Blockchain.

OpenZeppelin: For creating your ERC1155 Token standard contract, you’ll make use of an already created and secured contract on the openzeppelin library available remotely on the internet.

This tutorial requires you to have a solid foundation knowledge of basic web3 concepts like NFTs, openzeppelin, smart contracts, solidity code, etc.

Note: Although this tutorial will cover creating and deployment of your ERC contract, it will not include an in-depth explanation of how to create and deploy your ERC115 contract.

Requirements​

To follow this tutorial you should have the following:

  • Celo Wallet Extension: This tutorial will require you to have an account on an installed celo wallet extension, or if you’re using another wallet like metamask, you should have the celo alfajores network added. Here is a link to guide you on how to add the alfajores testnet to your custom wallet.

  • Faucets: You should also have your wallet funded with Celo test funds. Here is a link to request celo faucets.

  • Node & node package management npm or yarn: This tutorial will require you to use a preinstalled node package manager, yarn. You should also know about working with any package manager: npm or yarn.

  • IPFS: Although this tutorial will not cover uploading your NFT images on IPFS, you should also be familiar with the concept of uploading on IPFS, and how to upload files on IPFS.

Note: IPFS (InterPlanetary File System) is a distributed file system that allows users to store and access content from anywhere in the world. It is built on the same principles as the Ethereum blockchain, making it the ideal choice for hosting and delivering content on the blockchain.

The ERC115 Token Standard

Before getting started with writing code and minting your ERC115 token, here is a quick reminder on what the ERC1155 token is and how it is usually minted.

The ERC115 Token Standard is one of the most popular Ethereum-based token standards, that is used for creating, minting, storing, and transferring Non-Fungible Tokens (NFTs).

It is an extension of the ERC20 Token Standard and allows users to create unique tokens with different attributes, such as name, symbol, and metadata. And a powerful tool for developers to easily create and manage digital assets on a blockchain network.

Tokens created with the ERC1155 Token Standard are also interoperable with other ERC20 tokens, for the creation of multiple asset types, such as collectibles, game items, digital artwork, and more. To mint a new token, developers need to call the ERC1155 contract and pass in the token's contract address, an array of token IDs, and an array of tokenURI strings. The tokenURI string is used to identify each token and can contain information such as a token’s title, description, or image.

The following code example shows the functions to mint a new ERC1155 token from a smart contract on a blockchain network. From openzeppelin’s standard ERC1155 contract:

function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");

address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);

_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);

_afterTokenTransfer(operator, address(0), to, ids, amounts, data);

_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}

Uploading the NFT image on IPFS

To get started with minting your NFT on the Celo blockchain. First, you need to have your image uploaded on IPFS, although this tutorial will not cover how to upload your pictures, here is a video reference to learn how to upload your NFT images to IPFS using NFT UP, Since you will need an already uploaded NFT image for this tutorial, you can use the link to an NFT folder with the images PHENZIC000 and PHENZIC001, uploaded on IPFS.

Minting your ERC1155 Token Standard

Remix is an open-source web IDE that simplifies the process of creating and deploying smart contracts to a blockchain network. It offers a simple graphical user interface that enables you to write and deploy Ethereum contracts quickly and easily. To get started with minting and interacting with your tokens, you’ll need to create a basic template for your smart contract on Remix, hence the following steps:

  1. First, head over to the Remix IDE using this link,
  2. You will need to download the Celo plugin on Remix, for interacting, compiling, and deploying on the Celo blockchain. Following the images and steps below.
  • a. Click on the plug-like icon on the left bottom part of the screen.

  • b. Enter Celo into the search bar to search for the Celo plugin.

  • c. Click Activate to add the Celo plugin to the left plain, you will notice the Celo icon has been added to the plugins on the left, click on the Celo Icon.

plugin_manager

  1. Next, create a new file under the contracts directory, and name the file MyToken, where you will have your smart contract written.

files

  1. Copy and paste the following code below into your MyToken contract:
  • a. The code below simply initializes the contract by importing the standard ERC1155 token contract from openzeppelin into your contract, including all the functionalities of a standard ERC1155 token:
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

Note: OpenZeppelin is an open-source framework for developing secure, reliable, and upgradable smart contracts on the blockchain. It provides a large library of reusable, secure, and well-tested components that can be used to quickly develop and deploy applications on the Ethereum blockchain.

Copy and paste the code above inside your MyToken.sol contract file.

  • b. Next, the function below creates a new contract as a sub-contract of the standard openzeppelin ERC1155 contract and also initializes two constants for the NFTs you will be creating.
contract MyToken is ERC1155 {
uint256 public constant PHENZIC000 = 0;
uint256 public constant PHENZIC001 = 1;
}

Copy and add the code above to your MyToken contract file.

  • c. The Next function is the constructor function that takes in the link to your NFT images uploaded on IPFS and calls the _mints function to mint your NFTs.
    constructor() ERC1155("https://bafybeiaqqz4unoubpu2oz2rsgowh3irdqnpcqjoyspzwrepnrwql7rgvy4.ipfs.nftstorage.link/"){
_mint(msg.sender, PHENZIC000, 200, "");
_mint(msg.sender, PHENZIC001, 100, "");
}

Copy and add the code above to your token contract.

  • d. Finally the last function uri take in unit256 digit 0 or 1. And returns the direct link to any of the NFT's locations on IPFS, either the first no or the second.
    function uri(uint256 _tokenId) override public pure returns (string memory) {
return string(abi.encodePacked("https://bafybeiaqqz4unoubpu2oz2rsgowh3irdqnpcqjoyspzwrepnrwql7rgvy4.ipfs.nftstorage.link/PHENZIC00",Strings.toString(_tokenId),".jpeg")
);
}

Copy and add the code below inside your token contract.

  • e. Finally, your complete contract should look exactly like the one code below, you can copy and replace your entire code with this for uniformity's sake.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract MyToken is ERC1155 {
uint256 public constant PHENZIC000 = 0;
uint256 public constant PHENZIC001 = 1;

constructor() public ERC1155("https://bafybeiaqqz4unoubpu2oz2rsgowh3irdqnpcqjoyspzwrepnrwql7rgvy4.ipfs.nftstorage.link/"){
_mint(msg.sender, PHENZIC000, 200, "");
_mint(msg.sender, PHENZIC001, 100, "");
}

function uri(uint256 _tokenId) override public pure returns (string memory) {
return string(abi.encodePacked("https://bafybeiaqqz4unoubpu2oz2rsgowh3irdqnpcqjoyspzwrepnrwql7rgvy4.ipfs.nftstorage.link/PHENZIC00",Strings.toString(_tokenId),".jpeg")
);
}
}

  1. Next click on the Celo plug-in icon you activated earlier, to compile and deploy your contract on celo.

  2. Now, click on the connect button attached to the first input bar on the side plain, to connect to your wallet, you will notice it automatically returns the amount of native token on your alfajores wallet and also tells you the network you are on currently.

Note: Also ensure you are current on your celo alfajores testnet account on any wallet of your choice, and the account should already be funded with Celo testnet native token.

  1. Next, Click on the compile button, and Remix should compile the contract without any errors.

compile_token

  1. Now click on the deploy button below, and your wallet will pop up on the right side of your screen for you to sign the transaction.

deploy_myToken

  1. Click on confirm button, and you should have your contract already deployed to the celo alfajores testnet with the address of your contract on the display tab attached to the deploy button.

confirm_transaction

Note: The NFTs minted will be signed to the address of the person that deployed the contract and called the function.

  1. Now, that you have deployed your token contract with the minting function being called inside the contract’s constructor function. Your NFTs have automatically been minted to your address.

Interacting with the Deployed ERC1155 Token

Now that you have your newly minted ERC1155 token deployed and minted on Celo alfajores signed with your address. You can run some checks to interact with your token on the blockchain.

  • To check the number (balance) of NFT currently available on your wallet address:

a. Click on the function call balanceof at the bottom left side of your screen.

b. You will notice two drop-down input tabs to add your connected wallet address and the token Id of the NFT you want to view its balance, Copy your connected wallet address and past it in the first field, and input 0 in the second tab to view the balance of the NFT.

c. Click Call to view the amount of the PHENZIC000 token available in your address.

remix_balance_Of

  • Assuming you have a Gaming application where you welcome each new player by gifting them with the PHENZIC000 token and gifting the old-timer player with certain levels attained in the game with the PHENZIC001 token. You can have this done automatically on your Dapp but in this case, you can equally send these tokens by calling the safeTransferFrom function.

a. First, click on the safeTransferFrom function call like in the image below to add the required parameters to call the functions.

b. Copy and add your connected wallet address as the first input for the variable from.

c. Next, add the wallet address you want to send the NFT to, any remote alfajores address will work fine.

d. Enter the id of the token you want to send 0 or 1, and the amount you want to send for this example you can use 0 for id, 50 for the amount, and use 0x00 for the data input.

e. Click transact, and to check the balance of the remaining token.

f. Head back to the balanceof add your connected wallet address and 0 or the id of the token you sent from, and click call. You will notice the reduction in the token amount.

functions

  • The uri function call overrides simple taken in a token_id, and returns the link to either the first minted NFT or the second minter NFT on IPFS, depending on the token_id you input.

Conclusion

Minting your ERC1155 NFT on the Celo blockchain is a relatively straightforward process, but it does require some technical knowledge and setup. By following the steps outlined in this tutorial, you can successfully mint your ERC115 NFT and begin it in the Celo ecosystem.

You would have used the Celo plugin on the Remix IDE to interact with the Celo blockchain and, understand other concepts like how the IPSF works and how to mint your token contract using Remix.

About the Author

Mayowa Julius Ogungbola

is a Software Engineer and Technical writer always open to working on new ideas. I enjoy working on GitHub and you can also connect with me on LinkedIn.

Next Steps

Here are some other NFT-related tutorial articles you might be interested in:

References​

Here are links to some video tutorials you might be interested in following along with while reading this tutorial:

Go back

· 11 min read
Mayowa Julius Ogungbola

header

Introduction

The Celo blockchain is a proficient, fast, and lightweight platform that supports the building of innovative, complex, and client-designed mobile applications. In simple terms, it is a network that allows the development of decentralized and inventive mobile and web applications.

One of the core features of how the Celo blockchain work falls on the concept of multiple revolutionary solutions, one of which is called Proof of Stake (PoS). On completing this article, you’ll have a solid idea of the Concept of Celo’s protocols, what PoS is and how it makes Celo an efficient platform for creating indigenous, decentralized solutions.

You’ll also have all the basic information you’ll need to get started with building on the Celo blockchain.

Prerequisites

Throughout this article, you are not expected to have any prior in-depth knowledge of any technology or intricate detail about the web3 space. If you’re reading this tutorial, it means you want to know more about what a PoS is and how the Celo Blockchain integrates this verification system.

What is Consensus Mechanism

A Consensus Mechanism is a method of authentication adopted by blockchain platforms to ensure transactions are in sync and agree on which transaction is valid before adding the transaction to the blockchain.

Amongst others, one of the proven efficient and effective means of reaching consensus on the blockchain is using the Proof of Stack PoS consensus mechanism. Which is why the Celo blockchain uses it.

When a transaction is created on the Celo blockchain before it is added to the blockchain ledger it first needs to be validated by the chain’s miners, thus the need for consensus.

What is Proof of Stack (PoS)

Proof of Stack is a type of consensus mechanism that adopts the idea of staking coins to earn its node runners the right to validate a transaction before adding it to the blockchain ledger.

When a transaction occurs on a blockchain platform like Celo, there is a need to first authenticate and validate the transaction before adding the transaction to the blockchain permanently. These tasks are usually carried out by the blockchain’s miners and node runners on the Celo blockchain.

Note: Node runners or validators are individuals or companies running full blockchain network nodes. They provide the backbone of the blockchain network by providing the infrastructure that allows the network to process transactions and maintain a distributed ledger. Node runners are rewarded for their services with tokens or coins from the network.

Looking back to the technology system before the web3 revolutionary breakthrough, verifying transactions would require a centralized or automated entity that was prone to either time consumption for financial and data forgery for non-monetary transactions and a lot more.

The need for a system like the PoS for the validation of transactions, therefore came as an innovative technological breakthrough.

In a PoS system, the chances of forgery or manipulation of transactions and data would be easily spotted and penalized. The PoS means of consensus is one of the important features that make the Celo Blockchain a secure and rewarding platform for developers to build on and validators to manage and get rewarded for.

The PoS algorithm also allows its validators and node runners to carry out validation while maintaining a low computational cost and manual effort.

Other types of Consensus Mechanism

Just like the PoS is used by the Celo blockchain as a means of reaching Consensus in approving all transactions, there are also other consensus mechanisms adopted by other blockchains some of which are;

  1. Proof of Work (PoW): This type of consensus algorithm requires its miners to consume a massive amount of computational power to solve the complex cryptographic puzzle that defines each transaction. Although this algorithm is effective and adopted by popular blockchain platforms, it is not quite efficient.

  2. Delegated Proof of Stake (DPoS): This type of consensus algorithm is similar to the PoS algorithm. But unlike the PoS algorithm, a group of miners or node runners is tasked with achieving a distributed consensus. It serves more like a voting system where miners decide who should be responsible for validating a transaction.

  3. Proof Of Importance (PoI): This algorithm is also very similar to the Proof of Stake algorithm, In the mechanism rewards are given to effective network users. The algorithm allows a hierarchy of importance based on individuals who have made more contributions to the system and gives them the right to validate transactions on the platform.

  4. Proof of Activity (PoA): This algorithm is a hybrid of the Proof of Work (PoW) and the Proof of Stake (PoS) algorithms, that allow validators to stake coins as collateral, and also validate blocks using computational decryption and other resources.

  5. Proof of Captivity (PoC): Thissystem of consensus neither requires a miner si solve cryptographic puzzles like the PoW or stake coins like the PoS, but rather to prove that they have a certain amount of hard drive space to contribute to storing plots of cryptographic hashes for the blockchain.

  6. Proof of Authority (PoA): This algorithm simply pre-selects and authorizes validator nodes to validate a newly added block.

  7. Proof of Elapsed Time (PoET): This system of consensus was designed to simply select a leader from a pool of miners and validators and assigns the task of validating the next node.

These are a few other important consensus algorithms available and adopted by other blockchain platforms.

Other Core Feature Of the Celo Blockchain

  • Scalability: The Celo blockchain was built to handle a large number of users and transactions without slowing down or becoming unresponsive. Celo is a layer-1 blockchain solution that helps to scale up blockchain technology to handle a high throughput of transactions. It does this by allowing transactions to be validated off-chain and then quickly grouping them into batches for faster processing on-chain. This helps reduce overall network congestion and improves scalability.

  • Security: Transactions on the Celo Blockchain go through a set of measures taken to protect the data and transactions stored on the blockchain from tampering or unauthorized access. This includes encryption, secure protocols, and validation of transactions. Additionally, Celo's consensus protocol is designed to ensure the safety and integrity of the blockchain by maintaining a decentralized network of validators who are incentivized to keep the blockchain secure.

  • Speed: The Celo blockchain is designed to offer a high level of speed, and enables users to quickly and securely transfer value across the network in a matter of seconds. It also utilizes a sharding technology called Celo Fast Finality (CFF) that allows the network to split its transactions into multiple shards to process thousands of transactions per second. It utilizes a mechanism called instant finality that also allows the network to commit transactions.

Building on Celo

As a developer looking to build fast, secure, scalable, and innovative ideas, building on Celo is an exciting opportunity for you to create applications and services that leverage the Celo platform. Celo provides a secure, open-source platform for developers to create distributed applications and services that connect people and organizations in meaningful ways. With Celo, developers can create applications that bring new possibilities to the global economy, from unlocking financial inclusion to providing access to new markets.

Wallets

The Celo wallet enables its users to quickly and securely send and receive payments, store digital assets, and access various financial services. It allows users to view their account balances and track their transactions.

The wallet also provides developers with the tools they require to interact with the Celo Blockchain, like signing transactions, deploying and testing contracts, calling and testing functions, etc. Additionally, the Celo wallet offers enhanced security features to ensure the safety of users’ digital assets. With its suite of features, the Celo wallet is a powerful tool for building on the Celo blockchain. Indirectly you can also interact with the celo blockchain by adding the celo network to other wallets like metamask, etc. More wallet-related information and why you need one can be found here.

Smart Contract

The Celo blockchain also allows technological transactions like compiling, testing, debugging, deploying, and calling contracts on the network, which gives you the ability to create, a decentralized application and interact with a decentralized codebase on the blockchain.

Here, you will find more tutorials on creating, verifying, and deploying smart contracts, writing contract tests, and making contract call on the Celo blockchain using, Hardhat, Truffle, foundry, Remix, etc.

Connecting to Celo

When connecting to the Celo alfajores or maiNet for deploying or interacting with the network, you’re advised to use the Celo configuration file below in place of the code in your .config file.

require("@nomiclabs/hardhat-waffle");
require("dotenv").config({ path: ".env" });
require("hardhat-deploy");

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

// Prints the Celo accounts associated with the mnemonic in .env
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();

for (const account of accounts) {
console.log(account.address);
}
});

/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
defaultNetwork: "alfajores",
networks: {
localhost: {
url: "http://127.0.0.1:7545",
},
alfajores: {
gasPrice: 1500000000,
gas: 4100000,
url: "https://alfajores-forno.celo-testnet.org",
accounts: {
mnemonic: process.env.MNEMONIC,
path: "m/44'/52752'/0'/0",
},
//chainId: 44787
},
celo: {
url: "https://forno.celo.org",
accounts: {
mnemonic: process.env.MNEMONIC,
path: "m/44'/52752'/0'/0",
},
chainId: 42220,
},
},
solidity: "0.8.10",
};

Here is a link to the code sample above, you will also require your funded wallet’s Mnemonic phrase in an encrypted file. To know more about interacting with the Celo blockchain here are some tutorials you can read on.

Creating Dapps on the Celo blockchain

The Celo blockchain provides the infrastructure for decentralized applications (DApps) to act as a bridge between users and their data privacy. The increasing number of dApps that utilize the Celo blockchain validates its usefulness in the blockchain ecosystem.

Creating the Celo Blockchain is a revolutionary way to use blockchain technology to build secure and reliable mobile and web applications. With the Celo platform, developers can easily create decentralized applications (DApps) and smart contracts that enable users to transfer value, store information, and more. This platform is designed to be user-friendly and secure, making it an attractive option for developers looking to build the next generation of applications.

Redeploying to Celo

Redeploying your Decentralized Application (Dapp) to Celo is a necessary process that can enable you to take advantage of the Celo network’s advantages, including scalability and security. The Celo platform enables developers to create and deploy their Dapps quickly and easily, and redeploying is an important part of this process.

Redeploying your Dapp to Celo requires a Celo account, which you can create through their website or the Celo mobile app. Once you have an account, you must create a Dapp and deploy it to the Celo blockchain. This process involves setting up a smart contract, enabling the Celo network to run your Dapp. You then need to write the code for your Dapp and deploy it onto the Celo blockchain. Once your Dapp is deployed, it will be available to anyone on the Celo platform. Here you can find more information on Redeploying your Dapp to the Celo network.

Redeploying on the Celo network provides the following benefits to your decentralized application.

  1. Scalability: The Celo blockchain can scale to millions of users, with each user able to transact in a matter of seconds. This makes it ideal for large-scale applications that handle large volumes of transactions.

  2. Low Cost: Celo has committed to providing users with low-cost transactions, allowing developers to lower the cost of running their Dapps and making them more attractive to users.

  3. Security: Celo utilizes advanced cryptography to provide a secure and reliable platform for developers and users.

  4. Ease of Use: Celo has a user-friendly interface that makes it easy for developers to deploy their Dapps and for users to use them.

  5. Open Source: Celo is an open-source platform that allows developers to access and customize the code to their needs and requirements.

  6. Community Support: Celo has a vibrant and supportive community of developers and users who are always willing to help out and provide assistance.

Conclusion

Now that you have completed this tutorial, you understand the concept of one of the core concepts that make up the Celo blockchain. And you now have everything you need to start building and interacting with the celo blockchain.

Next Steps

Now that you’ve successfully grasped the lesson in this tutorial, you can also read on the following topics to help you get started on building real-world solutions and other development on Celo.

You can also consider contributing to the Celo network as a developer or as a technical writer (Celo Sage).

About the Author​

Mayowa Julius Ogungbola

A Software Engineer and technical writer who is always open to working on new ideas. I enjoy working on GitHub, and you can also find out what I tweet about and connect with me on LinkedIn

Go back

· 14 min read
Mayowa Julius Ogungbola

header

Introduction

When creating decentralized applications that leverage smart contracts, it is important to ensure that there are little or no vulnerabilities to prevent an attacker from compromising your application.

Unit testing helps you ensure that all functionalities in your contract are working as expected, and development environments like Truffle give you the same tools to help you write proficient tests for your contracts before final deployment.

In this tutorial, you’ll create an exemplary contract and learn how to write and run unit tests for your contract using the truffle development environment.

Prerequisites

Throughout this tutorial you’ll need to have worked with or have a basic knowledge of the following:

  • Truffle Suite: Truffle suite is a Development Environment that acts as a pipeline for interacting with the EVM and also provides essential features and valuable libraries for testing Ethereum smart contracts and makes it easy to interact with the blockchain.
  • Solidity: Solidity is a high-level programming language used for creating smart contracts.
  • Javascript: This tutorial will make use of Javascript, therefore you should be familiar with basic Javascript coding and algorithms.

Requirements

This tutorial also aspects that you have the following already installed or available:

  • Node & node package management npm or yarn: This tutorial will require you to use a preinstalled node package manager. You should also know about working with any package manager: npm or yarn.

Installing and setting up Truffle suite

To install the truffle suite using your terminal. Create a workspace, head over to the directory on your terminal, and run the command npm install -g truffle.

Now, run the command npx truffle init to fire up the development environment. You’ll notice a new file structure appears in your file explorer, something like the image below:

truffle_init

Running a Contract Test Simulation

To understand how unit testing works using the Truffle suite create a demo directory, different from your main directory, and run the command npx truffle unbox metacoin. The result of the successful run of the code should look like the image below.

creating_metacoin

The command starts up a demo project called <metacoin> including two contract files MetaCoin.sol and ConvertLib.sol in the contract directory and also has two testing files TestMetaCoin.sol and metacoin.js file in the test directory. For running unit tests on the metacoin contracts.

Now run the command npx truffle test and the result of the unit test should look exactly like the image below.

demo_testing

Truffle first compiles the contract, runs all the unit test in the test script, and returns the result of all the tests. The image above shows the result when it passes all the unit tests.

Creating the Smart Contract

Each contract test made is composed explicitly to test a specific contract, meaning if you have four different contract files in an application, your application should likewise have four test scripts for testing each contract. In the following steps, you'll write a simple sample contract which you'll, later be writing a for.

Note: If you’re new to solidity and creating smart contracts, check out this tutorial to get started and understand solidity code. The tutorial above also has a couple of functions that will help you learn how to write solidity code.

  1. Head back to the initial development environment directory you created; inside the contract folder, create a new file, Sample.sol. This will be the smart contract you’ll be writing unit tests for.

  2. The Sample.sol contract will have the following functionalities:

  • a. First, the contract is created, and the variables name, and age are also created and by default, have no value.
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

contract Sample {
string public name;
address public owner;


}
  • b. Next, the contract’s constructor function assigns the address of the deployer of the contract to the variable owner and assigns the string "deployer" to the name variable.

       constructor() {
    owner = msg.sender;
    name = "deployer";
    }
  • c. The next function rename accepts a string value as argument and assigns it to the variable name.

  
function rename(string memory _name) public {
name = _name;
}
  • d. The next function describe simply return the current values of the global variable, name.
  
function describe() public view returns (string memory) {
return (name);
}
  • e. Next is a modifier function ownerOnly that only allows the contract owner to call its parent function when added to any function.
    modifier ownerOnly() {
require(
msg.sender == owner,
"this function requires the owner of the contract to run"
);
_;
}
  • f. The following function changeOwner uses the previously created ownerOnly modifier to only allow the owner of the contract to change the role of the contract owner to any address by passing as an argument to the changeOwner function.
    function changeOwner(address _newOwner) public ownerOnly {
owner = _newOwner;
}
  • g. The next function deposit allows anyone to send a minimum of 1 ETH to the contract.
    function deposit() public payable {
require(
msg.value >= 0.01 * 10 ** 18,
"you need to send at least 0.01 ETH"
);
}
  • h. Finally, the last function in the Sample.sol contract allows anyone calling the contract to withdraw funds from the contract, as long as you pass in the number of tokens to withdraw as an argument. This transaction will also be terminated if the amount passed in exceeds than 10 ETH.
    function withdraw(uint256 _amount) public payable {
require(_amount <= 100000000000000000);
payable(msg.sender).transfer(_amount);
}

If you’ve completed your Sample.sol contract, Your smart contract should look exactly like the code below; You should update your contract with the code below for uniformity sake:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

contract Sample {
string public name;
address public owner;

constructor() {
owner = msg.sender;
name = "deployer";
}

function rename(string memory _name) public {
name = _name;
}

function describe() public view returns (string memory) {
return (name);
}

modifier ownerOnly() {
require(
msg.sender == owner,
"this function requires the owner of the contract to run"
);
_;
}

function changeOwner(address _newOwner) public ownerOnly {
owner = _newOwner;
}

function deposit() public payable {
require(
msg.value >= 0.01 * 10 ** 18,
"you need to send at least 0.01 ETH"
);
}

function withdraw(uint256 _amount) public payable {
require(_amount <= 100000000000000000);
payable(msg.sender).transfer(_amount);
}
}

To confirm you have no existing errors in your contract, run the command npx truffle compile on your terminal, and a successful result should look like the image below.

compiling_contract

Now that you know the different functions in the Sample.sol contract and you’re familiar with what they do. Next, you’ll learn how to create a unit test script to test subsections of the contract you just made.

Writing the Unit Test Script

Now that you have created the Sample.sol contract, you can begin writing the unit tests for the contract. After completing these tests, you’ll have a basic idea of how to create unit tests for smart contracts.

A very common pattern used when writing unit tests for smart contracts is:

a. Arrange: This is where you create dummy variables that you’ll need to run units of your test cases. They can be created globally after the contract test function s created or locally within the unit test.

b. Act: Next, is the part where you run your testing functions and store the result in a variable.

c. Assert: Since you already know the correct result of the test, then you compare your expected result with the response of the test you ran. If the test returns the expected result, it passes else, the test does not pass.

Also following the format:

 describe(<"functionName">, async function () {
beforeEach(async function() {
<what should happen before each test is run>
})
it("what the test is expected to do", async function () {
const response = <what was returned>
const result = <what should be returned>;
expect(response).to.equal(result); // compares the response to the expected result
});

Next, you’ll be creating a uint-test to test your Sample.sol contract using the previous format above, and you’ll learn how to create a basic unit test script on your own:

Testing a smart contract makes it easier to identify bugs and vulnerabilities and reduces the possibility of software errors that could lead to costly exploits. In the next few steps, you will learn the basic format of how to write unit tests based on your smart contract.

  • First, head over to the migrations folder and create a script file called 1_deploy_contract.js and copy the code below into the script.
const Sample = artifacts.require("Sample");
// const MetaCoin = artifacts.require("MetaCoin");

module.exports = function (deployer) {
// deployer.deploy(Sample);
// deployer.link(Sample, SampleTest);
// deployer.deploy(SamplTest);
deployer.deploy(Sample, { gas: 1000000 });
};

The code above is created to simply deploy your Sample.sol contract. Next, navigate to the test folder and create a new test script, SampleTest.js.

  1. Firstly, you’ll need to import the contract as a variable Sample in the first line of code.
const Sample = artifacts.require("Sample");
  1. Next, you’ll need to initialize the contract test with the following code below. This contract - Sample will cover all the unit test functions that will be carried out on the named contract.
contract("Sample", (accounts) => {
})
  1. Using the describe keyword to define a specific test for each function in the contract, you can carry out multiple tests using the it keyword for a specific function. The first test constructor tests the constructor function in the contract. Copy and add the code below.
  describe("constructor", async function () {
it("should have the correct name", async () => {
const sample = await Sample.deployed();
const name = await sample.name();
assert.equal(name, "deployer");
});

it("should have the correct owner", async () => {
const sample = await Sample.deployed();
const owner = await sample.owner();
assert.equal(owner, accounts[0]);
});
});

The function has two tests with string descriptions of what each of them is meant to do. The first test check for the initialization of the name variable and checks the value of the owner variable to the address of the deployer. The test passes if the result returns as expected and reverts with an error otherwise.

Now, run the command npx truffle test, and a successful result should look like the image below.

test(2)

  1. The next unit test describes the rename and describe function from the smart contract; the function carries out a single test on the rename and describe function. The test updates the name variable's value and checks the current the variable's current value if it has been updated. Copy and add the code below.
  describe("rename & describe", async function () {
it("should be able to rename", async () => {
const sample = await Sample.deployed();
await sample.rename("new name");
const name = await sample.describe();
assert.equal(name, "new name");
});
});

Now, run the command npx truffle test and a successful result should look like the image below.

test(1)

  1. The next unit test describes the changeOwner function in the smart contract; the test first uses the right address to attempt to change the owner, which should pass successfully. And then uses another random address to change the ownership role, which is meant to be reverted. Copy and add the code below.
describe("changeOwner", async function () {
it("should change the owner", async () => {
const sample = await Sample.deployed();
await sample.changeOwner(accounts[1], { from: accounts[0] });
const owner = await sample.owner();
assert.equal(owner, accounts[1]);
});

it("should not change the owner", async () => {
const sample = await Sample.deployed();
try {
await sample.changeOwner(accounts[2], { from: accounts[1]});
} catch (error) {
assert.equal(
error.message,
"VM Exception while processing transaction: revert"
)};
});
});

Now, run the command npx truffle test and a successful result should look like the image below.

test(3)

  1. The next function tests the deposit function of the contract. The first test will verify the deposit function works correctly which allows deposits of 0.01 ETH or greater. The second test verifies that the deposit function correctly rejects deposits of less than 0.01 ETH. Copy and paste the code below.
  describe("deposit", async function () {
it("should allow deposits", async () => {
const sample = await Sample.deployed();
await sample.deposit({ value: 0.01 * 10 ** 18 });
});
it("should not allow deposits below 0.01 ETH", async () => {
const sample = await Sample.deployed();
try {
await sample.deposit({ value: 0.009 * 10 ** 18 });
assert.fail("deposit should have failed");
} catch (error) {
assert.ok(error.message.includes("revert"));
}
});
});

Now, run the command npx truffle test and a successful result should look like the image below.

test(4)

  1. This next describe function tests the withdraw function in the contract. The first test is attempting to withdraw 0.01 ether from the contract. The second test is attempting to withdraw an amount greater than the balance to ensure that the withdrawal fails. If the test fails, it will return an error message with the word revert.
  describe("withdraw", async function () {
it("should allow withdrawals", async () => {
const sample = await Sample.deployed();
await sample.withdraw(BigInt(0.01 * 10 ** 18));
});
it("should not allow withdrawals above balance", async () => {
const sample = await Sample.deployed();
try {
await sample.withdraw(BigInt(0.01 * 10 ** 18));
assert.fail("withdrawal should have failed");
} catch (error) {
assert.ok(error.message.includes("revert"));
}
});
});

Finally, run the command npx truffle test and a successful result should look like the image below.

test(5)_

After completing your test script, your code should look exactly like the one below. For uniformity, sake replaces the entire code with this code test.

const Sample = artifacts.require("Sample");

contract("Sample", (accounts) => {
describe("constructor", async function () {
it("should have the correct name", async () => {
const sample = await Sample.deployed();
const name = await sample.name();
assert.equal(name, "deployer");
});

it("should have the correct owner", async () => {
const sample = await Sample.deployed();
const owner = await sample.owner();
assert.equal(owner, accounts[0]);
});
});

describe("rename & describe", async function () {
it("should be able to rename", async () => {
const sample = await Sample.deployed();
await sample.rename("new name");
const name = await sample.describe();
assert.equal(name, "new name");
});
});
describe("changeOwner", async function () {
it("should change the owner", async () => {
const sample = await Sample.deployed();
await sample.changeOwner(accounts[1], { from: accounts[0] });
const owner = await sample.owner();
assert.equal(owner, accounts[1]);
});

it("should not change the owner", async () => {
const sample = await Sample.deployed();
try {
await sample.changeOwner(accounts[2], { from: accounts[1] });
} catch (error) {
assert.equal(
error.message,
"VM Exception while processing transaction: revert"
);
}
});
});
describe("deposit", async function () {
it("should allow deposits", async () => {
const sample = await Sample.deployed();
await sample.deposit({ value: 0.01 * 10 ** 18 });
});
it("should not allow deposits below 0.01 ETH", async () => {
const sample = await Sample.deployed();
try {
await sample.deposit({ value: 0.009 * 10 ** 18 });
assert.fail("deposit should have failed");
} catch (error) {
assert.ok(error.message.includes("revert"));
}
});
});
describe("withdraw", async function () {
it("should allow withdrawals", async () => {
const sample = await Sample.deployed();
await sample.withdraw(BigInt(0.01 * 10 ** 18));
});
it("should not allow withdrawals above balance", async () => {
const sample = await Sample.deployed();
try {
await sample.withdraw(BigInt(0.01 * 10 ** 18));
assert.fail("withdrawal should have failed");
} catch (error) {
assert.ok(error.message.includes("revert"));
}
});
});
});

Conclusion

Writing unit tests for smart contracts can help a great deal in ensuring a secure and proficient contract, by suggesting fixes and improvements after discovering errors, issues, and security vulnerabilities in your contract. You have successfully created your unit test script for a simple sample contract using truffle. Now that you understand how unit tests are written, you can move on to writing more complex test scripts for other smart contracts. You can also read about how to run the unit test for smart contracts using Truffle.

Next Steps

Here is some other tutorial article.

Unit testing with Hardhat and Celo

How to create and Test contract calls with Celo and Hardhat

About the Author

Mayowa Julius Ogungbola

A Software Engineer and technical writer who is always open to working on new ideas. I enjoy working on GitHub, and you could also find out what I tweet about and connect with me on LinkedIn

References

Here is a link to the complete tutorial sample code on my GitHub, Leave a ⭐on the repository if you find it helpful.

Go back

· 12 min read

header

Introduction​

In this article, we'll cover how to build an on-chain DAO with Hardhat, Solidity, and JavaScript. If you're unaware, many crypto projects are attempting to utilize DAOs (decentralized autonomous organizations) for project governance. The decentralized nature of cryptocurrency makes DAOs a popular governance model in the blockchain industry.

With this one, we're getting right into the code. The DAO's governance token will be an ERC20 token. This ERC20 token will be used to create and vote on proposals.

Prerequisites​

You must be familiar with the following to fully understand this tutorial:

  • HardHat: Hardhat is an extensible developer tool that helps smart contract developers.
  • Solidity: A high-level programming language.
  • Javascript: You should be familiar with the language's fundamentals.
  • Chai: To test smart contracts, we'll use a javascript testing package.

Requirements​

To follow along, your computer must have the most recent version of Node.js installed. Ensure Node is at least version 16.0.0 or higher if it is already installed.

Hardhat Project Setup

Create a folder called DAO and open it in VS Code. Run the following command to set up a new hardhat project in the terminal:

npx hardhat .

Remove all the files from the contracts and test folder once the hardhat setup is complete. Create the following three files: DAO.sol, Token.sol, and dao.test.js. The contracts folder should have the .sol files, and the test folder should contain the .js file.

We will need to install some dev dependencies so that the hardhat project will work properly. To install those, run the following command in the terminal:

npm install --save-dev "hardhat@^2.12.2" "@nomicfoundation/hardhat-toolbox@^2.0.0" "solidity-coverage"

We will also need to install OpenZeppelin Library as dependency for our project:

npm install "@openzeppelin/contracts"

Let’s see what these can help us with:

  • hardhat: Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
  • @nomicfoundation/hardhat-toolbox: It bundles all the commonly used packages and Hardhat plugins.
  • solidity-coverage: It is a solidity code coverage plugin for Hardhat.
  • @openzeppelin/contracts: It is a secure smart contract library for solidity

Governance Token Smart Contract

Using the ERC20 token standard, we will develop a governance token for our DAO. Open Token.sol and insert the following code:

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

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

contract Token is ERC20 {

constructor() ERC20("Token", "TOKEN") {
_mint(msg.sender, 1000000);
}

}

Please go here to read more about how the above snippet works. We can now go to the smart contract for the DAO.

DAO Smart Contract

To get things going, we'll construct an empty smart contract called DAO in the DAO.sol file and import IERC20.sol from the openzeppelin library.

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

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

contract DAO {

}

Constructor Definition

Now that we have a blank smart contract, we can declare a variable inside of it with the name token of type IERC20.

The next step is creating the smart contract constructor, which takes the token's address as an argument. We keep the token address as a variable because we want our DAO contract to work with any ERC20 token. We will initialize the Token variable inside the constructor by assigning a value using the IERC20 interface and handing it an argument of the contract address.

contract DAO {

IERC20 public token;

constructor(address _token) {
token = IERC20(_token);
}
}

Defining Proposal

The option to propose and vote on proposals will be available to anyone who has a governance token and chooses to participate in the governance process. We must outline the structure of those proposals. A struct will define the format for each proposal we will create named Proposal.

    struct Proposal {
uint256 accept;
uint256 reject;
uint256 abstain;
uint256 deadline;
bytes32 title;
mapping(address => bool) voted;
}

Note: Everything other than functions goes above the constructor, and all the functions go below the constructor. Just a tip on organizing smart contracts

Storing Created Proposals

We need to store any proposal that is made. By creating a mapping called proposals, we will achieve this. The proposal will serve as the value, and the proposal's index, which we will track using the proposalIndex variable, will be the key in the mapping.

    uint256 public proposalIndex;
mapping(uint256 => Proposal) public proposals;

Create Proposal

After putting up everything related to proposals, there is just a function left that allows us to construct proposals. Starting off, let's define the function createProposal with visibility as public. This function returns the index of the created proposal (type uint256) and accepts the title (type bytes32) argument.

To access the proposals mapping with the current value of proposalIndex, we will first create a variable named proposal (with type Proposal and data location of storage). Then, by setting the value of the title (the function argument) and the current block timestamp plus one day, we will set the title and the deadline for the proposal we had gotten from the mapping. Finally, add 1 to proposalIndex's value and return the index of the proposal you just created.

    function createProposal(bytes32 _proposal) public returns(uint256) {
Proposal storage proposal = proposals[proposalIndex];

proposal.title = _proposal;
proposal.deadline = block.timestamp + 1 days;

proposalIndex++;

return proposalIndex - 1;
}

There is a problem; we only want governance token holders to have access to this service; we don't want anyone else to be able to create proposals. This can be resolved by introducing a modifier called onlyTokenHolders that checks to see if the caller's balance of their governance token is greater than zero and rejects the call otherwise. We can check the balance by using the token variable's balanceOf method.

    modifier onlyTokenHolders() {
require(
token.balanceOf(msg.sender) > 0,
"NOT_A_TOKEN_HOLDER"
);
_;
}

This modifier can be added to the createProposal function.

function createProposal(bytes32 _proposal) public onlyTokenHolders returns(uint256)

Vote on the Proposal

We've made it possible to propose, and now we can write a function so that token holders can vote on proposals. Only token holders who hadn't already voted before the deadline should be allowed to cast a vote.

The index of the proposal and the type of vote the token holder must cast are the two arguments this function should accept. We must limit the type of vote to just three options with an enum named Vote: accept, reject, and abstain.

    enum Vote {
accept,
reject,
abstain
}

We will fetch the proposal with the index supplied as an argument to the voteOnProposal function. Then, using the require statement, we'll see if the deadline has not passed or if the token holder has not already cast a vote. We shall set the voted flag for that token holder to true if all conditions are met. Following that, we will increase the number of votes cast by token holders' balance of governance tokens in accordance with their votes.

    function voteOnProposal(uint256 _index, Vote vote) public onlyTokenHolders {

Proposal storage proposal = proposals[_index];

require(block.timestamp < proposal.deadline, "INACTIVE_PROPOSAL");
require(proposal.voted[msg.sender] == false, "ALREADY_VOTED");

proposal.voted[msg.sender] = true;

if (vote == Vote.accept) {
proposal.accept += token.balanceOf(msg.sender);
} else if (vote == Vote.reject) {
proposal.reject += token.balanceOf(msg.sender);
} else {
proposal.abstain += token.balanceOf(msg.sender);
}

}

Execute Proposal

When the time has passed, the proposal will be executed by emitting an event. First, let's define the event called the winner. This event will take arguments, the proposal's index, its title, and the number of votes it received.

    event winner(uint256 _index, bytes32 proposal, Vote winningVote);

We must first determine whether the proposal is active; if it is, we cannot execute it. If not, we'll look at which votes received the most and run the event appropriately.

    function executeProposal(uint256 _index)  public {

Proposal storage proposal = proposals[_index];

require(block.timestamp > proposal.deadline, "ACTIVE_PROPOSAL");

if (proposal.accept >= proposal.reject) {
if (proposal.accept >= proposal.abstain) {
emit winner(_index, proposal.title, Vote.accept);
} else {
emit winner(_index, proposal.title, Vote.abstain);
}
}
else {
if (proposal.reject >= proposal.abstain){
emit winner(_index, proposal.title, Vote.reject);
} else{
emit winner(_index, proposal.title, Vote.abstain);
}
}
}

Testing Smart Contract

To test the contract, we're going to a chai and mocha library. To ascertain how many components of the smart contract have been tested, we will also use the hardhat coverage plugin. First, let's configure the hardhat coverage plugin. The only thing left to do is add the following to hardhat.config.js, as we have already installed hardhat coverage in the beginning:

require("solidity-coverage");

To determine how much of the part has been tested, enter the following command into the terminal:

npx hardhat coverage

This is the intended result:

Test result with zero coverage of the smart contract

Write some tests right away. You should feel comfortable running tests with chai and mocha. Some basics are as follows: Tests are created within it function and are arranged using describe. Please read this if you want to learn more about testing.

We'll use the AAA writing format, which stands for Arrange, Action, and Assert. We first create the necessary conditions for the test (arrange), then we perform the activity we are testing (act), and last, we evaluate if we are getting the results we were expecting (assert).

We need to do imports from chai, ethers, and @nomicfoundation/hardhat-network-helpers in the dao.test.js file.

const {
time,
loadFixture,
} = require("@nomicfoundation/hardhat-network-helpers");
const { expect } = require("chai");
const { ethers } = require("hardhat");

Create a describe block with the title DAO in the dao.test.js file. We will put all of our test cases, and fixtures in this describe block. contractFixture, a fixture we'll make, will deploy the contracts and generate test accounts. We'll also make our first proposal and transfer some tokens to various addresses for testing.

describe("DAO", function () {

async function contractFixture() {

const accounts = await ethers.getSigners();

const TOKEN = await ethers.getContractFactory('Token');
const token = await TOKEN.deploy();

await token.deployed();

const DAO = await ethers.getContractFactory("DAO");
const dao = await DAO.deploy(token.address);

await dao.deployed();


await token.transfer(accounts[1].address, 13000);
await token.transfer(accounts[2].address, 32300);

await dao.createProposal(ethers.utils.formatBytes32String("first proposal"))

return { accounts, token, dao };
}
// ALL TESTS SHOULD GO BETWEEN START AND END LINE
// ---------START---------


// ---------END---------
})

The deployment of contracts will be tested in the first test. To test this, we will see if the DAO contract has our governance token address properly set and if 1 million governance tokens are available.

    it("Should set right contract address", async function () {
const { token, dao } = await loadFixture(contractFixture);

expect(await dao.token()).to.equal(token.address);
expect(await token.totalSupply()).to.equal(1000000);
});

Now that we can test proposal-related functions. Let's first see if the proposal is being created correctly. To do this, we'll get the first proposal and then check that all the fields are filled in with the right data.

    it('Should create correct proposal', async function () {
const { accounts, dao } = await loadFixture(contractFixture);

const proposal = await dao.proposals(0);

expect(proposal.title).to.eq(ethers.utils.formatBytes32String("first proposal"));
expect(proposal.accept).to.eq(0);
expect(proposal.reject).to.eq(0);
expect(proposal.abstain).to.eq(0);
})

We can go ahead and check the voting function. We will load the fixture, make a proposal, and then vote on it to see if the number of votes increases due to our voting.

    it('Should have correct accept votes', async function () {
const { accounts, dao } = await loadFixture(contractFixture);

await dao.connect(accounts[1]).voteOnProposal(0, 0);
await dao.connect(accounts[2]).voteOnProposal(0, 0);

const proposal = await dao.proposals(0);

expect(proposal.accept).to.eq(45300);
})

We can check to see if someone is voting twice because it is not permitted. Voting twice should result in an error.

    it('Should revert with already voted', async function () {
const { accounts, dao } = await loadFixture(contractFixture);

await dao.voteOnProposal(0, 0);

await expect(dao.voteOnProposal(0, 0)).to.be.rejectedWith("ALREADY_VOTED");
})

The contract specifically forbids voting past the deadline, so now is a good time to put that condition to the test. With the help of the time module we imported from @nomicfoundation/hardhat-network-helpers, we may increase the block timestamp. We will vote after increasing the timestamp, which should result in an error.

    it('Should revert with inactive proposal', async function () {
const { accounts, dao } = await loadFixture(contractFixture);

await time.increase(time.duration.days(1));

await expect(dao.connect(accounts[1]).voteOnProposal(0, 0)).to.be.revertedWith("INACTIVE_PROPOSAL");
})

We can now go on to the testing executeProposal function. The timestamp must be larger than the deadline to run the executeProposal function. To test this, we will vote on the proposal, increase the timestamp, and then test to see which event is being emitted.

    it('Should emit event for first proposal on execution with accept vote as winner', async function () {
const { dao } = await loadFixture(contractFixture);

await dao.voteOnProposal(0, 0);
await time.increase(time.duration.days(1));

await expect(dao.executeProposal(0)).to.emit(dao, "winner").withArgs(0, ethers.utils.formatBytes32String("first proposal"), 0);
})

We can use the coverage command once more to run all of the tests and see how much of the smart contract we have tested. Output should resemble the following:

Test Result for partial coverage of the smart contract

Conclusion

You have successfully learned the following things from this article:

  • The on-chain DAO's workings
  • Interaction between ERC20 tokens from other contracts
  • Using Chai and Mocha to test solidity contracts
  • Use of the solidity coverage plugin

Next Steps​

We have tested the contract, but as you can see, we didn't test it completely. As a next step, apply what you learned in this article and work to reach as close to 100% test coverage as you can.

About the Author​

Nikhil Bhintade is the author of the article. Nikhil works as a product manager. He enjoys writing about cutting-edge technology and the things he is learning. You can see his most recent work on GitHub.

References​

Here is a reference to a project that was finished with tests that had 100% coverage. To understand more about using Hardhat, you may also refer to this page.

Go back

· 13 min read

header

🌱 Introduction

Welcome Developers, to the Step-by-Step Guide to Deploying your First Full-Stack Dapp on Celo! In this guide, we will walk you through the process of building and deploying a full-stack decentralized application (Dapp) on the Celo platform.

Celo is a decentralized platform that enables fast, secure, and scalable transactions on a global scale. It is built on top of the Ethereum blockchain and is designed to be easily accessible to developers and users alike.

Whether you are a seasoned blockchain developer or just getting started, this guide will provide the knowledge and tools you need to build and deploy your own Dapp on Celo.

So let's get started!

🗈 Prerequisites

  • A computer with an internet connection. You will need a computer with a stable internet connection to follow along with this guide.

  • Basic knowledge of programming. While we will provide step-by-step instructions, it will be helpful to have some basic knowledge of programming languages such as JavaScript and Solidity.

  • Node.js and npm installed. You will need to have Node.js and npm (the package manager for Node.js) installed on your computer. You can check if you have them installed by running the following commands in your terminal:

node -v
npm -v
  • A code editor. You will need a code editor to write and edit your code. Some popular options include Visual Studio Code and Atom.
  • A Metamask account. You will need a Metamask account to interact with the Celo blockchain from your web browser. If you don't already have one, you can create one by installing the Metamask extension for Chrome or Firefox.

⚠️ Requirements

  • Truffle: a development environment, testing framework, and asset pipeline for Ethereum
  • Node.js: a JavaScript runtime that allows you to run JavaScript on the command line
  • Yarn: a package manager for JavaScript
  • next: Next.js is a framework for building server-rendered or statically-exported React applications.
  • CeloCli - The celocli lets you interact with the Celo Protocol smart contracts.

What are NFTs?

Non-fungible tokens (NFTs) are digital assets that represent ownership of a unique item or concept. They are stored on a blockchain and cannot be exchanged for something else of equal value, like traditional currencies. NFTs are often used to represent digital art, collectibles, and other unique items and are bought and sold in online marketplaces. Their value is determined by their rarity and perceived value to collectors. NFTs provide a way to prove ownership and authenticity of digital assets and allow for the creation of scarcity in the digital world, which can increase the value of certain items. They are created using smart contracts on a blockchain platform, such as Ethereum, and are often represented as ERC-721 tokens.

Let's start building the future together!

Steps to set up the truffle project and its configs

  1. Install Node.js by following the instructions on the official website.

  2. Install Yarn by running the following command:

npm install -g yarn
  1. Install Truffle by running the following command:
yarn global add truffle
  1. Install HDWalletProvider by running the following command:
npm install @truffle/hdwallet-provider --save

image

  1. Install Celo Command Line Interface also install dotenv
npm install -g @celo/celocli
npm install dotenv
// dotenv will help us to load .env file as environment variables

  1. Create a new Truffle project by running the following command:
mkdir NFTsmartcontract
cd NFTsmartcontract
truffle init
tip

Learn more: If you are new to Truffle check out the Truffle docs.

This will create a new directory with the following structure:

NFTsmartcontract/
├── contracts/
│ └── Migrations.sol
├── migrations/
│ └── 1_initial_migration.js
├── test/
├── truffle-config.js
└── truffle.js
  1. Navigate to the truffle-config.js file in your project directory and Replace the following configuration for the Celo testnet:
const HDWalletProvider = require("@truffle/hdwallet-provider");
require("dotenv").config();
module.exports = {
contracts_directory: "./contracts",
contracts_build_directory: "./truffle_abis",
migrations_directory: "./migrations",
networks: {
local: {
host: "127.0.0.1",
port: 7545,
network_id: "*",
},
alfajores: {
provider: function () {
return new HDWalletProvider(
process.env.PRIVATE_KEY,
"https://alfajores-forno.celo-testnet.org"
);
},
network_id: 44787,
gas: 20000000, //make sure this gas allocation isn't over 20M, which is the max
},
celo: {
provider: function () {
return new HDWalletProvider(
process.env.PRIVATE_KEY,
"https://forno.celo.org"
);
},
network_id: 42220,
gas: 20000000, //make sure this gas allocation isn't over 20M, which is the max
},
},
mocha: {
// timeout: 100000
},
compilers: {
solc: {
version: "0.8.9", // Fetch exact version from solc-bin (default: truffle's version)
docker: false, // Use "0.5.1" you've installed locally with docker (default: false)
settings: {
// See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: false,
runs: 200,
},
evmVersion: "istanbul",
},
},
},
};
  1. Create A Celo Account using Celo Cli
celocli account:new

image

  1. Create a .env File in the root directory and add PRIVATE_KEY that we got from Celocli Command or You can use PRIVATE_KEY from Metamask.
PRIVATE_KEY="62dda1a6a6ee2dasdasdsadasdassdas1e2200095661a1b1e9dsadsdsdsadasasd"
  1. Create .gitignore file

It is important to hide your mnemonic and other important files while developing applications. When using Git or GitHub, you can populate a .gitignore file with the code below to ensure you don’t accidentally publish these files.

# dependencies
/node_modules

# Mac users
.DS_Store

#hidden files
.env

Now we are done with setting up truffle

Steps for creating ERC721 Contract and Truffle Migration File

  1. Create a NFT.sol file in contracts/ folder.

  2. Add the following code to it.

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract NFT is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;

Counters.Counter private _tokenIdCounter;
constructor() ERC721("NFTexample", "CELO") {}

function safeMint(string memory uri) public {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, uri);
}

// The following functions are overrides required by Solidity.

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}

function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
}
tip

Use the OpenZeppelin Wizard to easily create, deploy, and interact with smart contracts using the OpenZeppelin library.

  1. Install openzeppelin Library by running this command in the root folder.
npm install @openzeppelin/contracts
  1. Create a 2_deploy_contracts.js File in the migrations folder and follow the code.
const NFT = artifacts.require("NFT");
module.exports = function(deployer) {
deployer.deploy(NFT);
};

Now we are done creating NFT.sol And its Migration Config, next we gonna deploy it on Celo testnet Blockchain

Steps to deploy Smart Contract

  1. We need Faucet For deploying smart contracts on Celo Blockchain. Use Celo Testnet Faucet to get faucet money input your address which we got from celocli.

  2. Now we Gonna Compile the Smart Contract and Check if there are any problems with it.

truffle compile
  1. After successful Compilation We Now gonna deploy it on Celo Testnet
//Truffle migrate compiles AND migrates your contract. In the future, you can run truffle migrate to complete both steps but run only if you are deploying it on a Local server.

truffle migrate
// use truffle deploy --network network name to deploy on celo testnet
truffle deploy --network alfajores
//We can use other Chain as well by adding them in truffle-config.js

image

  1. After We Got our Smart Contract Address we can check it on Celo Blockchain explorer using Block Explorer.

ヾ(´⌣`)ノ Hurray we Deployed our First ERC721 Smart Contract Make Sure to Save the Smart Contract Address in a File We Gonna use it in Our Frontend. As For Our Smart Contract we have deployed is smart contract

Frontend using NextJS

Steps

  1. Set up a Next.js project:

Install Next.js and create a new Next.js project by running the following commands:

npm init next-app
cd next-app
npm run dev

image

  1. Install Ethers.js:

Ethers.js is a JavaScript library that allows you to interact with the Ethereum blockchain. To install it, run the following command in your terminal:

npm install ethers
  1. Install React-Bootstrap
npm install react-bootstrap bootstrap

image

  1. Now we have installed react-bootstrap we need to add its CSS to the \_app.js file.
import 'bootstrap/dist/css/bootstrap.min.css';

  1. Now We have our libraries which we gonna use. Now let's edit our pages/index.js file
import React from 'react';
import { ethers } from 'ethers';
import contractAbi from "./abi/NFT.json";
import { Button, Card, Container, Nav, Navbar } from 'react-bootstrap';

const contractAddress = '0xa5Dcc3EB1eC8E417A4eA6CA51bBE4119D323d6E4'; // Replace with your contract address
function App() {
const [walletAddress, setWalletAddress] = React.useState(null);

const connectToWallet = async () => {
try {
await window.ethereum.enable();
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} catch (error) {
console.error(error);
}
};

const mintNFT = async () => {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum)
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, contractAbi.abi, signer);

// Replace with Your metadata
const metadata = 'https://ipfs.io/ipfs/QmTvsVaaHTuMNmwXgbfgkrztFEazAPyzmrb4VSS2PbqLjA?filename=the-chainlink-elf.json';

const result = await contract.safeMint(metadata, { from: walletAddress });
console.log(result);
} catch (error) {
console.error(error);
}
};

const disconnectFromWallet = async () => {
try {
await window.ethereum.request({ method: 'eth_requestAccounts', accounts: [] });
setWalletAddress(null);
console.log('Wallet disconnected');
} catch (error) {
console.error(error);
}
};
React.useEffect(() => {
const checkWalletConnection = async () => {
if (window.ethereum.isConnected()) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} else {
console.log('Wallet not connected');
}
};
checkWalletConnection();
}, []);
return (
<div style={{ backgroundColor: 'white' }}>
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">NFT Minter</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
{ !walletAddress ?
<Nav.Link href="#" onClick={connectToWallet}>Connect</Nav.Link> :
<Nav.Link href="#" onClick={disconnectFromWallet}>Disconnect</Nav.Link>
}
<Nav.Link href="viewnft">View NFTs</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Container>
<Card style={{ width: '18rem' }} className="mx-auto mt-5">
<Card.Img variant="top" src={`https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png`} />
<Card.Body>
{walletAddress && (<>
<Button variant="primary" onClick={mintNFT}>Mint NFT</Button>
</>
)}
</Card.Body>
</Card>
</Container>
</div>
);
}

export default App;
  1. We need to copy the Abi Json file from our smart contract folder truffle_abis/NFT.json and Create a new folder in our pages folder named ABI and paste it over there.

  2. Now we can run Our Command to Test it out.

npm run dev

image

ヾ(´⌣`)ノ Hurray We are done with our frontend Mint Function.

Let's Complete Our Tutorial With View NFT

Steps To Create View NFT's Page

  1. Create a new file in pages with the name viewnft.js

  2. Install axios in the project to fetch data from IPFS JSON link.

npm install axios
  1. Let's import all the files which we needed
import React from 'react';
import { ethers } from 'ethers';
import contractAbi from "./abi/NFT.json";
import { Button, Card, Container, Nav, Navbar,Row,Col } from 'react-bootstrap';
import axios from 'axios';
  1. After importing all files we gonna create functions to fetch all user nfts.
async function listTokensOfOwner() {
const provider = new ethers.providers.Web3Provider(window.ethereum)

const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);
//we are using logs to fetch users nft
const sentLogs = await contract.queryFilter(
contract.filters.Transfer(walletAddress, null),
);
const receivedLogs = await contract.queryFilter(
contract.filters.Transfer(null, walletAddress),
);

const logs = sentLogs.concat(receivedLogs)
.sort(
(a, b) =>
a.blockNumber - b.blockNumber ||
a.transactionIndex - b.TransactionIndex,
);

const owned = new Set();

for (const log of logs) {
const { from, to, tokenId } = log.args;

if (addressEqual(to, walletAddress)) {
owned.add(tokenId.toString());
} else if (addressEqual(from, walletAddress)) {
owned.delete(tokenId.toString());
}
}

const uri = [];
for (const own of owned) {
const tokenuri = await tokenUri(own);
const response = await axios.get(tokenuri);

uri.push(response.data)
}
setuserNFT(uri);
};
async function tokenUri(id){
//this function is to fetch tokenUri from smart contract
const provider = new ethers.providers.Web3Provider(window.ethereum)
const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);
const url =await contract.tokenURI(id);
return url.toString()
}
function addressEqual(a, b) {
//this functoin is for checking the address match because sometime metamask and our input wallet addresses are in different Cases.
return a.toLowerCase() === b.toLowerCase();
}
  1. Let's look at our Complete `viewnft.js`` How it looks.
import React from 'react';
import { ethers } from 'ethers';
import contractAbi from "./abi/NFT.json";
import { Button, Card, Container, Nav, Navbar,Row,Col } from 'react-bootstrap';
import axios from 'axios';

const contractAddress = '0xa5Dcc3EB1eC8E417A4eA6CA51bBE4119D323d6E4'; // Replace with your contract address
function App() {
const [walletAddress, setWalletAddress] = React.useState(null);
const [userNFt,setuserNFT] = React.useState(null);
const connectToWallet = async () => {
try {
await window.ethereum.enable();
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} catch (error) {
console.error(error);
}
};

const disconnectFromWallet = async () => {
try {
await window.ethereum.request({ method: 'eth_requestAccounts', accounts: [] });
setWalletAddress(null);
console.log('Wallet disconnected');
} catch (error) {
console.error(error);
}
};
React.useEffect(() => {
const checkWalletConnection = async () => {
if (window.ethereum.isConnected()) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} else {
console.log('Wallet not connected');
}
};
checkWalletConnection();
if(walletAddress){
listTokensOfOwner();
}
}, [walletAddress]);
async function listTokensOfOwner() {
const provider = new ethers.providers.Web3Provider(window.ethereum)

const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);

const sentLogs = await contract.queryFilter(
contract.filters.Transfer(walletAddress, null),
);
const receivedLogs = await contract.queryFilter(
contract.filters.Transfer(null, walletAddress),
);

const logs = sentLogs.concat(receivedLogs)
.sort(
(a, b) =>
a.blockNumber - b.blockNumber ||
a.transactionIndex - b.TransactionIndex,
);

const owned = new Set();

for (const log of logs) {
const { from, to, tokenId } = log.args;

if (addressEqual(to, walletAddress)) {
owned.add(tokenId.toString());
} else if (addressEqual(from, walletAddress)) {
owned.delete(tokenId.toString());
}
}

const uri = [];
for (const own of owned) {
const tokenuri = await tokenUri(own);
const response = await axios.get(tokenuri);

uri.push(response.data)
}
setuserNFT(uri);
};
console.log(userNFt)
async function tokenUri(id){
const provider = new ethers.providers.Web3Provider(window.ethereum)

const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);
const url =await contract.tokenURI(id);
return url.toString()
}
function addressEqual(a, b) {
return a.toLowerCase() === b.toLowerCase();
}
return (
<div style={{ backgroundColor: 'white' }}>
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">NFT Minter</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
{!walletAddress? <><Nav.Link href="#" onClick={connectToWallet}>Connect</Nav.Link></>:
<><Nav.Link href="#" onClick={disconnectFromWallet}>Disconnect</Nav.Link></>}
<Nav.Link href="/viewnft">View NFTs</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Container>
{walletAddress && (<>

<Row xs={1} md={4} className="g-4">
{userNFt && userNFt.map((item,i)=>{return(
<Col>
<Card style={{ width: '18rem' }} className="mx-auto mt-5">
<Card.Img variant="top" src={item.image}/>
<Card.Body>
<Card.Title>{item.name}</Card.Title>
<Card.Text>
{item.description}
</Card.Text>
</Card.Body>
</Card></Col>
) })}
</Row>
</>
)}
</Container>
</div>
);
}

export default App;
  1. Now we again gonna run npm run dev to start our application locally.

image

🥳🥳🥳 Congratulations! 🥳🥳🥳 You have successfully deployed your full stack Dapp on the Celo blockchain.

Conclusion

In conclusion, the full stack dApp we have built using Next.js, Ether.js, and a smart contract is a powerful tool for creating decentralized applications. The use of a smart contract on the Ethereum blockchain ensures that our dApp is transparent, secure, and immutable, while the frontend built with Next.js allows for a smooth and intuitive user experience. The combination of these technologies has allowed us to create a truly decentralized application that can be used by anyone, anywhere, at any time. With the potential to revolutionize the way we interact and transact online, the future looks bright for dApps like ours.

About Author

Hi! My name is Kunal Dawar and I am a Full Stack web2/web3 Developer. I have participated in numerous hackathons and have been fortunate enough to win many of them.

One thing that I am truly passionate about is creating things that are reliable and don't break easily. I believe that creating high-quality products is important not only for the users but also for the overall growth and success of a business.

In my free time, I enjoy learning about new technologies and staying up-to-date with the latest trends in the field. I also love to share my knowledge with others and mentor those who are interested in pursuing a career in web development.