Introduction
The Toucan Protocol is a decentralized platform that enables businesses and individuals to offset their carbon footprint by purchasing carbon credits from verified environmental projects. You can learn more about Toucan here https://toucan.earth/. In this tutorial, we will build a dApp(carbon offset marketplace) that interacts with the Toucan Protocol’s carbon credits smart contract using Celo Composer. Celo Composer is a tool that streamlines the creation and deployment of Celo blockchain applications.
Prerequisites
Before starting this tutorial, make sure you have the following installed on your machine:
- Node.js
- NPM (Node Package Manager)
Creating the Project
Open your terminal and run the following command to create a new Celo Composer project
npx @celo/celo-composer create
You will be prompted to select the framework you want to work with. In this case, we are using React.
You will also be prompted to pick a web3 library for the react app. In this tutorial, rainbowkit is chosen
Next up, you will be prompted to choose the smart contract framework, we will be using hardhat in this tutorial.
Finally, you will be asked if you want to create a subgraph, we don’t need to create one so we can select no. However, we will be interacting with Toucan Protocol’s contract via a subgraph
Pick your project name as well
Starting out the Front-End
Implementing Toucan Infrastructure
Each TCO2 is representative of a carbon offset. To read more about TCO2, check it out here TCO2 - Toucan. We will be creating a carbon offset marketplace that will display the first 12 TCO2s on Celo network. To get all TCO2s, all we have to do is check deployed Toucan subgraphs.
So, we head over to https://docs.toucan.earth/toucan/dev-resources/other/subgraph and we see a list of all subgraphs deployed, our focus is on the one deployed on Celo. If this is your first time hearing about subgraphs you can check out the docs here
Here is the subgraph on Celo test network
From this subgraph, let’s run a quick query in the Playground,
{
tco2Tokens {
name
symbol
score
createdAt
creationTx
creator {
id
}
}
}
This will give a list of all carbon offsets. This is what we need to simulate a marketplace.
Building out the frontend
In our frontend, we will be using apollo client to makes calls to our subgraphs.
Navigate to the react-app folder and run the following commands
yarn add graphql
yarn add @apollo/client
Now head over to your _app.tsx and update it by importing your apollo client, this way
import { ApolloProvider } from "@apollo/client";
import client from "../apollo-client";
Also, wrap your layout with the client by adding this piece of code.
<ApolloProvider client={client}>
<Layout>
<Component {...pageProps} />
</Layout>
</ApolloProvider>
Still in the root folder of react-app, create a js file and name it apollo-client.js
and the copy the following to it
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: "https://api.thegraph.com/subgraphs/name/toucanprotocol/alfajores",
cache: new InMemoryCache(),
});
export default client;
Create a component and name it CarbonOffsets.tsx, here we will be writing our query for the subgraph using apollo client
Import gql
and useQuery
import { gql, useQuery } from "@apollo/client";
To call the query, and for the sake of this demo, we will just be querying the first 12 carbon offsets.
const CARBON_OFFSETS = gql`
query CarbonOffsets {
tco2Tokens(first: 12) {
name
symbol
score
createdAt
creationTx
creator {
id
}
}
}
`;
Proceeding to write an instance of the component, we can do something like
const CarbonOffsets = () => {
const { loading, error, data } = useQuery(CARBON_OFFSETS);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error! {error.message}</div>;
//your custom code
}
In the div you want to display your data, we need to do some mapping
<div className="mt-6 grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-8 clickable-card">
{data.tco2Tokens.map((carbon: any) => (
<div
key={carbon.id}
className="group relative max-w-sm rounded overflow-hidden shadow-lg"
>
<div className="min-h-80 aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-md bg-gray-200 group-hover:opacity-75 lg:aspect-none lg:h-80">
<img
src={randomizeImage()}
alt={randomizeImage()}
/>
</div>
<div className="mt-4 flex justify-between pl-4">
<div>
<h3 className="text-bg font-weight-bold text-gray-900">
<Link
href={`https://celoscan.io/tx/${carbon.creationTx}`}
>
<span aria-hidden="true" className="absolute inset-0" />
Name: {carbon.name}
</Link>
</h3>
<p className="mt-2 text-sm text-gray-500">
Symbol: {carbon.symbol}
</p>
<p className="mt-2 text-sm font-medium text-gray-900 pr-3">
Score: {carbon.score}
</p>
</div>
</div>
<div className="mt-4 flex pl-4">
<span className="inline-block bg-gray-200 rounded-full mt-2 px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">
Created At{" "}
{new Date(carbon.createdAt * 1000).toLocaleString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "2-digit",
hour: "numeric",
minute: "numeric",
hour12: false,
})}
</span>
</div>
</div>
))}
</div>
The next step is to update the index.tsx. Your code can look like this
import React from "react";
import CarbonOffsets from "../components/CarbonOffsets";
export default function Home(): JSX.Element {
return (
<div>
<CarbonOffsets />
</div>
);
}
Go ahead to run the application by running the following command in the terminal
npm run dev
Here is what you should expect to see, basically a collection of carbon offsets including their names, symbol and time it was created. Clicking on it also takes you to the transaction on the Celo explorer
Clicking on the first one for example, we see the transaction and can check through the logs to see more information such as vintagetokenId and tokenAddress.
Moving on, we can also interact with some functionalities from Toucan’s smart contract. One of which is the RedeemAuto
function. To do this, we can use the SDK provided by the Toucan team.
Add the sdk to your project by running this command
npm i toucan-sdk
Create a new component and you can name it CarbonRedeem.tsx
Call the instance of the ToucanClient like this
const sdk = new ToucanClient("mumbai");
Because the RedeemAuto function involves us writing to the blockchain, we need to set a signer. To do this, add this to your code
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
sdk.setProvider(provider);
sdk.setSigner(signer);
Our task is basically to redeem a pool of tco2tokens so we call a query to get all a list from the subgraph and populate them in a dropdown. As above, remember to import your gql client
import { gql, useQuery } from "@apollo/client";
const GET_TCOTOKENS = gql`
query CarbonRedeem {
tco2Tokens {
symbol
}
}
`;
<select
className="bg-gray-100 rounded-none py-2 px-4 mr-2"
value={selectedToken}
onChange={handleTokenSelect}
>
<option value="">Select Token</option>
{data.tco2Tokens.map((token: any) => (
<option key={token.id} value={token.id}>
{token.symbol}
</option>
))}
</select>
There is also a function to interact with the contract through the sdk which might look like this. We are sending the symbo; of the pool token and the amount to redeem
const redeemAuto = async () => {
try {
const ethereum: any = window;
if (typeof window.ethereum !== 'undefined') {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
console.log("signer", signer);
const sdk = new ToucanClient("mumbai");
sdk.setProvider(provider);
sdk.setSigner(signer);
const amountBN = BigNumber.from(amount);
const contractReceipt = await sdk.redeemAuto(
selectedToken,
parseEther(amount.toString())
);
console.log(contractReceipt);
setcontractReceipt(contractReceipt);
}
else {
// `window.ethereum` is not available, so the user may not have a web3-enabled browser
console.error('Please install MetaMask or another web3-enabled browser extension');
}
} catch (error) {
console.error(error);
}
};
Finally, this is our full code
import React, { useState } from "react";
import Link from "next/link";
import { gql, useQuery } from "@apollo/client";
import { ToucanClient } from "toucan-sdk";
import { BigNumber, Contract, ContractReceipt, ethers } from "ethers";
import { parseEther } from "ethers/lib/utils.js";
const GET_TCOTOKENS = gql`
query CarbonRedeem {
tco2Tokens {
symbol
}
}
`;
const CarbonRedeem: React.FC = () => {
const [amount, setAmount] = useState<number>(0);
const [contractReceipt, setcontractReceipt] = useState<ContractReceipt>();
const [selectedToken, setSelectedToken] = useState<any>("");
const { loading, error, data } = useQuery(GET_TCOTOKENS);
require("dotenv").config();
const redeemAuto = async () => {
try {
const ethereum: any = window;
if (typeof window.ethereum !== 'undefined') {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
console.log("signer", signer);
const sdk = new ToucanClient("mumbai");
sdk.setProvider(provider);
sdk.setSigner(signer);
const amountBN = BigNumber.from(amount);
const contractReceipt = await sdk.redeemAuto(
selectedToken,
parseEther(amount.toString())
);
console.log(contractReceipt);
setcontractReceipt(contractReceipt);
}
else {
// `window.ethereum` is not available, so the user may not have a web3-enabled browser
console.error('Please install MetaMask or another web3-enabled browser extension');
}
} catch (error) {
console.error(error);
}
};
const handleTokenSelect = (event:any) => {
setSelectedToken(event.target.value);
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div className="flex items-center mb-3">
<label className="block text-gray-700 font-bold mb-2" htmlFor="amount">
Amount(minimum value is 1):
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="amount"
type="text"
value={amount}
onChange={(e) => setAmount(Number(e.target.value))}
/>
<select
className="bg-gray-100 rounded-none py-2 px-4 mr-2"
value={selectedToken}
onChange={handleTokenSelect}
>
<option value="">Select Token</option>
{data.tco2Tokens.map((token: any) => (
<option key={token.id} value={token.id}>
{token.symbol}
</option>
))}
</select>
<button
className="bg-blue-500 hover:bg-blue-400 text-white font-bold py-2 px-4 rounded"
onClick={redeemAuto}
>
Redeem
</button>
{contractReceipt && (
<div>
<Link href={`https://alfajores.celoscan.io/tx/${contractReceipt.transactionHash}`} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-700 underline">
View transaction details {contractReceipt.contractAddress}
</Link>
</div>
)}
</div>
);
};
export default CarbonRedeem;
Proceed to check your browser for changes. Make sure to have funds in your Celo testnet wallet. If you don’t have a Celo Wallet, you can add it to your Metamask here or add the Celo wallet plugin to your browser.
This is what you should expect
Fill and select an option
Click on Redeem
After a successful transaction, you will see a link to the transaction details
You can click on that and look through it
Congratulations you have just created a dApp using Toucan Protocol and Celo Composer.