Financing Eco-friendly Initiatives using Toucan and Celo Composer

Financing Eco-friendly Initiatives using Toucan and Celo Composer https://celo.academy/uploads/default/optimized/2X/3/37be0aac9ea9291b3e6055d9cf2643d3d1d6f450_2_1024x576.jpeg
none 0.0 0

We are going to build a platform that enables individuals and businesses to finance eco-friendly initiatives as a way of offsetting their carbon footprint.

Consequently, NFTs are issued to supporters and serve as an incentive to reduce carbon emissions. The more carbon credits are retired, the juicier the NFT reward.

At the end of the tutorial, users should be able to create a carbon credit trading DApp on Toucan using Celo Composer. Users can view and retire carbon credit tokens, which will help to achieve a sustainable future.

Prerequisites

To successfully follow along in this tutorial, you need basic knowledge of the following:

  • HTML, CSS, and React
  • Blockchain, Solidity, and Hardhat

Requirements​

What does it mean to support Eco-friendly initiatives using Toucan?

Means that you can support environmentally friendly projects by using Toucan’s platform to offset carbon emissions. Carbon credits are a way to measure and reduce carbon dioxide (CO2) emissions. By purchasing carbon credits through Toucan, you can contribute financially to projects that actively work towards reducing greenhouse gas emissions and promoting sustainable practices. These projects may include renewable energy initiatives, reforestation efforts, or other eco-friendly endeavors. By offsetting carbon credits using Toucan, you are directly contributing to the financing and success of these environmentally conscious initiatives.

Now that we are all on same page, let’s dive into the technical part.

The setup

Since we are going to be using Celo Composer, let’s start by running npx @celo/celo-composer@latest create If you are not familiar with Celo Composer, feel free to watch this video

Choose React for the Frontend framework prompt, Hardhat for the smart contract prompt, No for subgraph, and finally enter your project name. I will give this project, green-init

If the installation goes well, you should have hardhat and react-app directory, directly inside packages folder.

Smart Contract

Create a file inside hardhat/contracts directory and name it GreenInit.sol or any name of your choice. Paste below solidity code inside the just created file:

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

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

contract GreenInit is
    ERC721,
    ERC721Enumerable,
    ERC721URIStorage
{

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    mapping(address => bool) public userMinted;


    constructor() ERC721("GreenInitiative", "GNT") {}

    function _baseURI() internal pure override returns (string memory) {
        return "https://ipfs.io/ipfs/QmPc2Yp3YeS8JWnrq7eHJp5LBnDPdyM3A2TJFiGPFKhnfH/";
    }

    // 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);
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId, batchSize);
    }

    function issueNFT(address _nftAddress, uint256 _retireCounts) public {

        if (userMinted[msg.sender]) {

        uint256 tokenId = ERC721Enumerable(_nftAddress).tokenOfOwnerByIndex(msg.sender, 0);

        // Check if user can be upgraded.

        if (_retireCounts < 2) {
            string memory uri = "nft1.json";
            _setTokenURI(tokenId, uri);
        } else if (_retireCounts < 3) {
            string memory uri = "nft2.json";
            _setTokenURI(tokenId, uri);
        } else {
            string memory uri = "nft3.json";
            _setTokenURI(tokenId, uri);
        }

        } else {

            uint256 tokenId = _tokenIds.current();
            string memory uri = "nft1.json";
            _safeMint(msg.sender, tokenId);
            _setTokenURI(tokenId, uri);
            userMinted[msg.sender] = true;
            _tokenIds.increment();
        }

    }


    function getTokenId() public view returns (uint256) {

        require(userMinted[msg.sender], "User has not NFT");

        // User will always have 1 token.
        uint256 tokenIndex = 0;

        return tokenOfOwnerByIndex(msg.sender, tokenIndex);

    }



}

We needed to implement some functions in the code seeing we imported a couple of smart contract from Open Zeppelin. We are going to focus on the issueNFT function.

The function takes two parameters: _nftAddress, which represents the address of the ERC721 contract, and _retireCounts, which indicates the number of times the user has retired carbon tokens .

If the user has already minted an NFT, the function retrieves the token ID of the user’s NFT by calling tokenOfOwnerByIndex on the ERC721 contract (_nftAddress).

Based on the value of _retireCounts, the function determines the appropriate URI to set for the NFT. If _retireCounts is less than 2, it sets the URI to nft1.json. If it’s between 2 and 3, it sets the URI to nft2.json. Otherwise, for values greater than or equal to 3, it sets the URI to nft3.json. We have to keep the numbers low for the purpose of this tutorial.

If the user has not minted an NFT before, the function generates a new token ID (_tokenIds.current()) and mints a new NFT using _safeMint with msg.sender as the recipient.

The function sets the URI for the minted NFT using _setTokenURI, assigns userMinted[msg.sender] to true to indicate that the user has minted an NFT, and increments the _tokenIds counter.

The Frontend

The CarbonOffset.tsx file is at the hub of the frontend application and that is the only component that we are going to be creating, the rest are helpers.js and interact.js in utils directory.

Copy and past the below code in CarbonOffset.tsx file:

import { gql, useQuery } from "@apollo/client";
import ToucanClient from 'toucan-sdk'
import { issueNFT, hasNCT, addressToLowerCase } from '../utils'
import { useProvider, useSigner, useAccount } from 'wagmi'
import Image from 'next/image'
import {parseEther} from "ethers/lib/utils";

const CARBON_OFFSETS = gql`
  query CarbonOffsets {
    tco2Tokens(first: 3) {
      name
      symbol
      score
      createdAt
      creationTx
      creator {
        id
      }
    }
  }
`

interface CarbonOffsetProps {
  getNftHandler: () => void;
}


const CarbonOffsets: React.FC<CarbonOffsetProps> = ({getNftHandler}) => {

  const provider = useProvider()
  const { data: signer, isError, isLoading } = useSigner()
  const toucan = new ToucanClient('alfajores', provider)
  signer && toucan.setSigner(signer)

  const { address } = useAccount()
  const { loading, error, data } = useQuery(CARBON_OFFSETS)

  const getUserRetirements = async() => {
    return await toucan.fetchUserRetirements(addressToLowerCase(address))
  }

  const redeemPoolToken = async (): Promise<void> => {

    const redeemedTokenAddress = await toucan.redeemAuto2('NCT', parseEther('1'))
    return redeemedTokenAddress[0].address

  }

  const retireTco2Token = async (): Promise<void> => {
    try {
      const tco2Address = await redeemPoolToken()

      return await toucan.retire(parseEther('1'), tco2Address)
    } catch (e) {
      console.log(e)
    }
  }

  const supportProject = async() => {

    if (!address) return alert('Connect Wallet')

    const userHasNCT = await hasNCT(address)
    if (userHasNCT) {
      const res = await retireTco2Token()
      return res && (await issueNFTHandler())
    }
    alert('Purchase NCT token first')
  }

  const issueNFTHandler = async () => {

    if (!address) return alert('Connect your Celo wallet')

    const retirements = await getUserRetirements()

    if (retirements) {
      await issueNFT(retirements.length)
      await getNftHandler()
    }
  }

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error! {error.message}</div>

  return (

    <div className="grid grid-cols-3 gap-4 mt-10">
      {data.tco2Tokens.map((carbon: any, i: number) => (
      <div key={i}>
        <div className="border border-gradient-to-r from-pink-700 via-blue-800 to-red-800 border-1 rounded-lg shadow-lg p-4">
          <Image
            src={`/images/${i}.jpg`}
            width={300}
            height={300}
            alt="NFT image"
            className="mx-auto"
          />
          <h2 className="font-bold mb-2 mt-3 text-gray-400">{carbon.name}</h2>
          <button onClick={supportProject} type="button" className="bg-green-600 text-gray-100 font-bold w-full rounded">
            Support
          </button>
        </div>
      </div>
    ))}
  </div>

  )
}

export default CarbonOffsets

We imported various dependencies and utility functions, such as @apollo/client for GraphQL queries, toucan-sdk for interacting with the Toucan ecosystem, and other utility functions from utils.

We start by defining a GraphQL query CARBON_OFFSETS to fetch carbon offset tokens.

We define the CarbonOffsets component, which receives a prop called getNftHandler as a function. Within the component, it makes use of hooks such as useProvider, useSigner, and useAccount from the wagmi library to interact with the Celo provider and retrieve signer and account information.

Then, we will create an instance of the ToucanClient from the toucan-sdk and sets the signer using the obtained signer from useSigner.

The component uses the useQuery hook from @apollo/client to fetch the data for CARBON_OFFSETS GraphQL query.

We also have getUserRetirements, redeemPoolToken, retireTco2Token, supportProject, issueNFTHandler to interact with the Toucan ecosystem and perform actions like retiring tokens, redeeming pool tokens, supporting projects, and issuing NFTs.

Based on the loading and error status of the query, the component renders either a loading message, an error message, or the retrieved data.

Inside the render part of the component, it iterates over the data.tco2Tokens array and displays each carbon offset token as a card with an image, name, and a support button.

When the support button is clicked, the supportProject function is called, which checks if the user has enough NCT tokens and then proceeds to retire a token and issue an NFT.
The getNftHandler function is called to fetch the NFT after the issuance process.

interact.js

import { providers, Contract } from 'ethers'
import axios  from 'axios'
import GreenInit from '../GreenInit.json'

export const contractAddress = '0x0976833ca8F68b7453e59Ae3bb3bf871a174D09e'

export async function getContract() {

  let contract

  try {
    const { ethereum } = window

    const provider = new providers.Web3Provider(ethereum)
    const signer = provider.getSigner()
    contract = new Contract(contractAddress, GreenInit.abi, signer)

  } catch (error) {
    console.log("ERROR:", error)
  }
  return contract
}

export const issueNFT = async (retireCount) => {

  try {
    const contract = await getContract()

    let res = await contract.issueNFT(contractAddress, retireCount)
    return await res.wait()
  } catch (e) {
    console.log(e)
  }
}

export const getNFT = async () => {

  try {

    const contract = await getContract()
    const tokenId = await contract.getTokenId()
    const tokenURI = await contract.tokenURI(tokenId)
    return await getNFTMeta(tokenURI)

  } catch (e) {
    console.log(e)
  }
}

export const getNFTMeta = async URI => {
  try {
    return (await axios.get(URI)).data
  } catch (e) {
    console.log({ e })
  }
}



providers and Contract are imported from the ethers library, while axios is imported for making HTTP requests to the NFT URI.

The contractAddress holds the Celo smart contract address we want to interact with.
While getContract function is responsible for creating an instance of the contract. It uses the Web3Provider from ethers and window.ethereum to get the provider and signer. The contractAddress and the contract’s ABI from the GreenInit.json file are used to create the contract instance. If any errors occur during the process, they will be logged to the console.

issueNFT function is used to issue an NFT by calling the issueNFT function of the contract. It first retrieves the contract instance using the getContract function, then calls the issueNFT function on the contract with the contractAddress and retireCount as parameters. The result is then waited for using the res.wait() method. If any errors occur during the process, they will be logged to the console.

getNFT function is responsible for retrieving the NFT metadata. We will first retrieve the contract instance using the getContract function. We then call the getTokenId function on the contract to get the token ID. Next, we will call the tokenURI function on the contract with the token ID to get the URI of the NFT metadata. Finally, getNFTMeta function, to retrieve the NFT metadata using the URI. If any errors occur during the process, they will be logged to the console.

getNFTMeta function is used to retrieve NFT metadata by making an HTTP GET request using Axios. It takes the URI as a parameter and returns the response data. If any errors occur during the HTTP request, they will be logged to the console.

Conclusion

We have seen how Toucan is a powerful tool that can be used to help create a more sustainable future. By supporting eco-friendly initiatives, offsetting carbon footprints, trading carbon credits, issuing NFTs, and retiring carbon credit tokens, Toucan can help to reduce greenhouse gas emissions and promote sustainability.

Here is the link to the complete code. Here is the demo.

About the Author​

A software engineer, co-founder, Africinnovate, and a Web3 enthusiast. Connect with me on LinkedIn and Github

References​

7 Likes

Approved congratulations on the hackathon!

4 Likes

Looking forward to this tutorial

3 Likes

Congratulations brother. You have done well.

4 Likes

Good job brother, not bad at allđź‘Ť

2 Likes

Interesting

2 Likes

This is a wonderful project and would make a good scalable business. I hope it’s getting all attention it deserves

2 Likes

Well done

1 Like