How to build a Simple Waste Marketplace on Celo blockchain - Part 1

How to build a Simple Waste Marketplace on Celo blockchain - Part 1 https://celo.academy/uploads/default/optimized/2X/f/fb30e3c9de6a279413b30cfaa3b35847bcea7de6_2_1024x576.png
none 0.0 0

Introduction

Building a decentralized application (dApp) on a blockchain like Celo presents a unique opportunity to construct peer-to-peer systems for a wide array of use-cases. This tutorial guides you through developing a simple Waste Marketplace on the Celo blockchain using Hardhat, Solidity, and JavaScript.

Prerequisites

Before proceeding, you should have the following:

  • Basic understanding of Solidity
  • Basic knowledge of JavaScript
  • Node.js and npm installed
  • Familiarity with Hardhat
  • Celo Wallet
  • Basic understanding of Blockchain and Celo

Step 1: Setting Up the Development Environment

We’ll start by installing Hardhat, an Ethereum development environment. To install Hardhat, open your terminal and type:

mkdir WasteMarket
cd WasteMarket
mkdir hardhat
cd hardhat
npm init --yes
npm install --save-dev hardhat
npm install --save-dev @nomicfoundation/hardhat-toolbox @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers

After that, create a new Hardhat project:

npx hardhat

Your project directory should look like this

Step 2: Writing the Smart Contract

Let’s write our marketplace contract in Solidity. Create a new file in the contracts folder and name it WasteMarket.sol.

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

contract WasteMarket {

    // Define the structure of the waste object
    struct Waste {
        string wasteType;
        uint weight;
        address payable seller;
        bool isSold;
    }

    // Define a dynamic array of Waste objects
    Waste[] public wastes;

    // Function to sell waste, adds a new waste object to the array
    function sellWaste(string memory wasteType, uint weight) public {
        wastes.push(Waste({
            wasteType: wasteType,
            weight: weight,
            seller: payable(msg.sender), // the address of the sender becomes the seller
            isSold: false
        }));
    }

    // Function to buy waste, which transfers the funds to the seller
    // and marks the waste as sold
    function buyWaste(uint wasteId) public payable {
        Waste storage waste = wastes[wasteId]; // gets the waste object using its id
        require(!waste.isSold, "This waste is already sold!"); // checks if the waste is not sold
        waste.seller.transfer(msg.value); // transfers funds to the seller
        waste.isSold = true; // marks the waste as sold
    }

    // Function to get the total count of wastes listed in the marketplace
    function getWasteCount() public view returns (uint) {
        return wastes.length;
    }

    // Function to retrieve the details of a specific waste by its index
    function getWaste(uint index) public view returns (string memory wasteType, uint weight, address seller, bool isSold) {
        Waste storage waste = wastes[index];
        return (waste.wasteType, waste.weight, waste.seller, waste.isSold);
    }
}

This smart contract enables users to sell waste (with specific type and weight) and buy listed waste.

Let’s explain each code block in detail:

Contract Declaration:

contract WasteMarket {
...
}

This defines our contract, WasteMarket. The contract is the fundamental building block of Ethereum applications - it’s similar to a class in object-oriented programming.

Struct Definition:

struct Waste {
    string wasteType;
    uint weight;
    uint price;
    address payable seller;
    bool isSold;
}

Here we’re defining a new Solidity structure or struct which is a complex datatype. This struct is called Waste and it represents a waste item in our marketplace. It has the following properties:

  • wasteType: The type of waste (e.g., “Plastic”, “Metal”, etc.). It’s a string.
  • weight: The weight of the waste item. It’s a uint (unsigned integer), so it cannot be negative.
  • price: The price of the waste item.
  • seller: The address of the seller. address payable means it’s an address you can send funds to.
  • isSold: A boolean (true/false) representing whether the waste has been sold.

Array Definition:

Waste[] public wastes;

This line defines a public array wastes of Waste structs. public means that this variable can be read from outside the contract. Solidity will automatically create a getter function for it.

sellWaste Function:

function sellWaste(string memory wasteType, uint weight, uint price) public {
    wastes.push(Waste({
        wasteType: wasteType,
        weight: weight,
        price: price,
        seller: payable(msg.sender),
        isSold: false
    }));
}

This function allows a user to sell waste. It takes the waste type, weight, and price as input, creates a new Waste struct from these, and adds it to the wastes array. The msg.sender represents the address of the user who called this function (i.e., the seller).

buyWaste Function:

function buyWaste(uint wasteId) public payable {
    Waste storage waste = wastes[wasteId];
    require(!waste.isSold, "This waste is already sold!");
    require(msg.value >= waste.price, "Insufficient funds sent!");
    waste.seller.transfer(msg.value);
    waste.isSold = true;
}

The buyWaste function allows a user to buy a waste item. It takes as input wasteId, the index of the waste item in the wastes array. The keyword payable indicates that this function will make a transfer of funds (Ether). Two requirements are checked:

  • The waste item must not already be sold.
  • The buyer sent enough funds to cover the price of the item.

If these conditions are met, the funds are transferred to the seller, and the waste item is marked as sold.

getWasteCount Function:

function getWasteCount() public view returns (uint) {
    return wastes.length;
}

The getWasteCount function is a view function (it doesn’t modify the contract’s state) that returns the number of waste items currently listed in the marketplace.

getWaste Function:

function getWaste(uint index) public view returns (string memory wasteType, uint weight, address seller, bool isSold) {
    Waste storage

Step 3: Compiling the Smart Contract

To compile the contract, run the following command:

npx hardhat compile

waste3

Step 4: Deploying the Smart Contract on the Celo Alfajores Testnet

Create a new file deploy.js inside the scripts folder:


async function main() {
    // Get the first signer from the signer array
    const [deployer] = await ethers.getSigners();
  
    console.log("Deploying the contract with the account:", deployer.address);
  
    // Get the contract factory
    const Contract = await ethers.getContractFactory("WasteMarket");

    // Deploy the contract
    const contract = await Contract.deploy();
  
    console.log("Contract deployed to:", contract.address);
}
  
main()
    .then(() => process.exit(0)) // exits the process successfully
    .catch(error => {
        console.error(error); // logs error if any occurs
        process.exit(1); // exits the process with failure
    });

The deployment script, written in JavaScript, is responsible for deploying the WasteMarket contract to the local Hardhat network. After the script is run, it logs the address of the account used to deploy the contract and the address of the deployed contract. These addresses can be used to interact with the contract in subsequent transactions. Here’s a breakdown of what the script does:

  • const [deployer] = await ethers.getSigners(): This line retrieves an array of signers available in our Hardhat environment. The first signer in the list (the deployer) usually controls the most Ether in the Hardhat network.

  • console.log("Deploying the contract with the account:", deployer.address): This logs the Ethereum address of the deployer, which is the account used to deploy the contract.

  • const Contract = await ethers.getContractFactory("WasteMarket"): This gets the contract factory for the WasteMarket contract. In ethers.js, a ContractFactory is an abstraction used to deploy new smart contracts.

  • const contract = await Contract.deploy(): This line deploys the contract to the Hardhat network by initiating a transaction that deploys a new instance of the contract to the network.

  • console.log("Contract deployed to:", contract.address): Finally, the script logs the address of the deployed contract to the console.

  • The main() function is then called, and it handles any errors that may occur during the deployment process. If the deployment is successful, the process exits with a success status (0). If there’s an error, it logs the error to the console and exits with a failure status (1).

Now, we need to add the Celo Alfajores network configuration in hardhat.config.js:

require("@nomiclabs/hardhat-waffle");

// Add the alfajores network to the configuration
module.exports = {
  solidity: "0.8.17",
  networks: {
    alfajores: {
      url: "https://alfajores-forno.celo-testnet.org",
      accounts: {
        mnemonic: "ADD_MNEMONIC_KEY",
        path: "m/44'/52752'/0'/0",
      },
      chainId: 44787,
    },
  },
};

This configuration file is specific to Hardhat, a popular Ethereum development environment. The setup in this file configures your environment to connect to the Celo Alfajores test network and use a specific version of the Solidity compiler. Here’s what each part of the file does:

  • require("@nomiclabs/hardhat-waffle"): This imports the Hardhat Waffle plugin. Hardhat Waffle integrates Waffle’s Ethereum testing functionality into Hardhat’s development environment.

  • module.exports: The module exports a configuration object for Hardhat. This object is how you customize your Hardhat environment.

  • solidity: "0.8.17": This specifies the version of Solidity that your smart contracts are written in.

  • networks: This object specifies the networks that Hardhat will connect to.

  • alfajores: This specifies the Celo Alfajores test network as a target for deploying your smart contracts.

  • url: "https://alfajores-forno.celo-testnet.org": This is the URL of the JSON-RPC API of the Alfajores network.

  • accounts: This specifies the Ethereum accounts that will be used to deploy your contracts to the Alfajores network. It uses a mnemonic phrase to generate these accounts.

  • mnemonic: "ADD_MNEMONIC_KEY": You replace “ADD_MNEMONIC_KEY” with your mnemonic phrase.

  • path: "m/44'/52752'/0'/0": This is the derivation path for the accounts. It’s a way of deriving multiple keys from a single seed phrase (the mnemonic).

  • chainId: 44787: This specifies the chain ID of the Alfajores network. It’s used to ensure that you’re signing transactions for the correct network.

You could also use a .env file to store your Metamask mnemonic key, just make sure you install the dotenv dependency.

To deploy the contract to the Celo Alfajores network, run:

npx hardhat run scripts/deploy.js --network alfajores

WASTE4

Step 5: Interacting with the Contract

Let’s interact with our contract using JavaScript. Here is an example of how you might do it:

const { ethers } = require("ethers");

// Create a provider connected to the Celo Alfajores testnet
const provider = new ethers.providers.JsonRpcProvider("https://alfajores-forno.celo-testnet.org");

// Create a signer with your private key and the provider
const signer = new ethers.Wallet("your-private-key").connect(provider);

// Specify the address of your deployed contract
const contractAddress = "your-deployed-contract-address";

// Specify the ABI of your contract
const abi = [ /* the ABI from your contract goes here */ ];

// Create a contract instance
const contract = new ethers.Contract(contractAddress, abi, provider);

const main = async () => {
    // Call the sellWaste function on the contract
    const tx = await contract.connect(signer).sellWaste("Plastic", 500);
    // Wait for the transaction to be confirmed
    await tx.wait();

    // Fetch the first waste object from the contract
    const waste = await contract.wastes(0);
    // Log the fetched waste object
    console.log(waste);
}

main();

This script is written in JavaScript and is used to interact with a deployed smart contract on the Celo Alfajores test network. Here is a step-by-step explanation:

  • const { ethers } = require("ethers"): This line imports the ethers.js library, which is a complete Ethereum library and wallet implementation in JavaScript.

  • const provider = new ethers.providers.JsonRpcProvider("https://alfajores-forno.celo-testnet.org"): This line creates a new ethers.js provider that is connected to the Celo Alfajores test network. The provider allows us to interact with the Ethereum blockchain; it’s essentially our connection to the Ethereum network.

  • const signer = new ethers.Wallet("your-private-key").connect(provider): Here, we create a wallet object with a private key and connect it to our provider. The wallet object is known as a signer in ethers.js terminology because it can sign transactions, which means it has the ability to execute transactions on the Ethereum network.

  • const contractAddress = "your-deployed-contract-address": Here, we specify the Ethereum address where our smart contract is deployed. Replace "your-deployed-contract-address" with the actual address of your deployed contract.

  • const abi = [ /* the ABI from your contract goes here */ ]: Here, we specify the Application Binary Interface (ABI) of our contract. The ABI is an array that describes how to interact with our contract. It’s like a list of functions, their arguments, and their return values.

  • const contract = new ethers.Contract(contractAddress, abi, provider): Here, we create a new contract instance with the contract’s address, ABI, and our provider. This instance allows us to interact with our deployed contract on the Ethereum network.

  • const main = async () => { /* ... */ }: Here, we define an asynchronous function named main. This function is where we will interact with our contract.

  • const tx = await contract.connect(signer).sellWaste("Plastic", 500): Inside the main function, we connect our signer to our contract and call the sellWaste function with a type of "Plastic" and a weight of 500. This creates a new transaction on the Ethereum network.

  • await tx.wait(): This line waits for the transaction to be mined and confirmed by the Ethereum network.

  • const waste = await contract.wastes(0): This line fetches the first waste object from our contract. It does this by calling the wastes function on our contract with an argument of 0.

  • console.log(waste): This line logs the fetched waste object to the console. The waste object includes the type, weight, seller, and isSold properties of the waste.

  • main(): Finally, we call the main function to execute our script.

Conclusion

Now, you’ve created a simple Waste Marketplace built on the Celo blockchain! You’ve learned how to write, compile, deploy, and interact with a smart contract on Celo.

Next Step

We continue our journey on building our marketplace in the Part 2. We’ll create a frontend dApp using Next.js, a React framework.

About the Author

I’m a Web3 Fullstack developer and Technical writer. You can connect with me on LinkedIn, GitHub, Twitter.

Reference

4 Likes

This appears to be a tutorial rather than a pathway. Removing the pathway tag.

3 Likes

Gas Optimization Techniques in Solidity on Celo - Tutorials - Celo Academy

Optimizing Gas Consumption in Celo Smart Contracts A Step-by-Step Guide - Tutorials - Celo Academy

Multiple tutorials on this topic already

@Jorshimayor check them out

5 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.

5 Likes

@Jorshimayor i’ll be reviewing this

2 Likes

good to go @Jorshimayor

3 Likes

Great One @Jorshimayor

2 Likes