DEV Community

Jerry Musaga
Jerry Musaga

Posted on

How to mint tokens from the backend using Nest.js on Celo Alfajores network

Overview

In this tutorial, you will learn how to

  • create an ERC20 token smart contract from openzeppelin wizard with mint functionality
  • deploy the contract to the Alfajore's testnet on Celo
  • create API to fetch methods from the deployed smart contract to the backend
  • use the celo-ethers-wrapper to make ethers compatible with celo network

Prerequisite

  • To follow up this tutorial, a basic understanding on API and nestjs is necessary.

Generate smart contract from Hardhat

  • Open up your terminal and create an empty folder using this command mkdir my-project && cd my-project Then run yarn init -y to initialize the project and then install hardhat using the command yarn add --dev hardhat.
    Image description

  • After that, run yarn hardhat and select Create a typescript project and leave every other option as the default.

Image description

  • Open up the project folder on visual studio code or any other text editor. It should look like this

Image description

  • In the contracts folder, delete the Lock.sol file and do so with the files in the scripts and tests folder too.

  • Next up, we will be using the openzeppelin contract library to generate the ERC20 token, instead of writing the code from
    scratch. But first, we need to install it using this command yarn add @openzeppelin/contracts on the terminal. After the installation, go to openzeppelin wizard to generate the contract for your token. After selecting mintable, it should look like this

Image description

  • On your text editor, in your contracts folder, create a new file called MyToken.sol and copy the codes from the openzeppelin wizard and paste it into the newly created file. Your file should look like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

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

contract MyToken is ERC20, Ownable {
    constructor() ERC20("MyToken", "MTK") {}

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • The mint function in your contract is for creating the total supply of that token to the wallet address specified in the function

  • This is a basic solidity smart contract with the functionality of a GET and POST requests that we can use in the backend.

  • On your terminal, run the command yarn hardhat compile so as to compile the smart contract and generate the artefact folder, which is the JSON file containing useful informations about the smart contract. We will need this JSON object for the backend interaction with the contract.

Deploy smart contract to Alfajores network

We will be using hardhat to deploy our smart contract by setting up celo in our local environment.

  • In your text editor, open up hardhat.config.ts file and add up this celo configuration code below
const config: HardhatUserConfig = {
  defaultNetwork: "alfajores",
  networks: {
    alfajores: {
      url: "https://alfajores-forno.celo-testnet.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0"
      },
    }
  },
  solidity: "0.8.18",
};
Enter fullscreen mode Exit fullscreen mode
  • In your project root directory, create a .env file where we will be storing our mnemonics. It should look like this
MNEMONICS = "paste your 12 mnemonic keys here"
Enter fullscreen mode Exit fullscreen mode
  • Next up, install the dotenv using this command yarn add dotenv so we can access the mnemonic variable in the config file. And then import the dotenv into the file
import * as dotenv from 'dotenv';
dotenv.config()
Enter fullscreen mode Exit fullscreen mode
  • Your hardhat.config.ts file should look like this
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

import * as dotenv from 'dotenv';
dotenv.config()

const config: HardhatUserConfig = {
  defaultNetwork: "alfajores",
  networks: {
    alfajores: {
      url: "https://alfajores-forno.celo-testnet.org",
      accounts: {
        mnemonic: process.env.MNEMONIC,
        path: "m/44'/52752'/0'/0"
      },
    }
  },
  solidity: "0.8.18",
};

export default config;

Enter fullscreen mode Exit fullscreen mode
  • To deploy our smart contract, go to your scripts folder, create a file and name it deploy.ts , copy & paste the code snippets below into it
import { ethers } from "hardhat";
import { MyToken__factory } from "../typechain-types";

async function main() {
  const [signer] =  await ethers.getSigners();
  console.log("Deploying contracts with the account:", signer.address);
  console.log("Account balance:", (await signer.getBalance()).toString()); //get the wallet balance of the signer account
  const contractFactory = new MyToken__factory(signer);

  console.log(`Deploying MyToken Contract...\n`);
  const contract = await contractFactory.deploy();
  const contractTxnReceipt = await contract.deployTransaction.wait();
  console.log(`Deployed contract at address ${contractTxnReceipt.contractAddress}`);


}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Enter fullscreen mode Exit fullscreen mode
  • Run this command on your terminal to deploy the contract yarn hardhat run scripts/deploy.ts --network alfajores and you should get an output similar to this

Image description

  • If you get an error showing insufficient gas, copy your address to the celo faucet and fund your account with test tokens.

  • Take note of the deployed contract address because it will be useful when making API calls to the backend

Setting up the backend using Nest.js

Nest.js is a framework for building Node.js server side applications.
Let's get started

  • In your terminal, run this command npm i -g @nestjs/cli to install nest cli globally.
  • In your my-project directory, run nest new backend which will create a new folder consisting of the base structure of Nest files. After installation using yarn, your terminal should look like this

Image description

Image description

  • In the terminal, navigate into the backend folder using the command cd backend and run yarn start:dev to start up the server and keep it in watch mode.

  • Open up your browser and run http://localhost:3000 and you will see the output of Hello World!, which means the server is running successfully.

  • Let us install a package that will help in describing our RESTful APIs, nestjs/swagger. Run the command yarn add @nestjs/swagger to install.

  • After the installation process is complete, in your src folder, open up the main.ts file and paste the code below into it

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('Mint ERC20 Token on Celo Blockchain')
    .setDescription('The ERC20 API description')
    .setVersion('1.0')
    .addTag('celo, alfajores testnet')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode
  • Open up your browser and enter the url http://localhost:3000/api. You will get an output like this

Image description

Integrating Smart contract to the Backend

  • We will be using the ethers library. Run the command yarn add ethers@^5.7.2 to install ethers.
  • Remember the artefact folder we generated after we compiled the smart contract? We will copy the contents in MyToken.json file into our backend folder.
  • In the backend/src folder, create a new folder called assets and create a new file MyToken.json, paste the copied contents into the file.
  • Open up app.service.ts, import ethers and the json object. Copy and paste the code below into the file
import { Injectable } from '@nestjs/common';
import { ethers } from 'ethers';
import * as MyTokenJson from './assets/MyToken.json';

@Injectable()
export class AppService {

}
Enter fullscreen mode Exit fullscreen mode
  • We have a problem in importing Json, so we have to fix that by adding "resolveJsonModule": true to tsconfig.json file.

Image description

  • Remember the contract address we got after deploying the smart contract from the script file in hardhat, we will be using that address to interact with the functions of that address. For now, let's store the address in the app.service.ts file. The file should look like this
import { Injectable } from '@nestjs/common';
import { ethers } from 'ethers';
import * as MyTokenJson from './assets/MyToken.json';

const CONTRACT_ADDRESS = "0xBEc9321DEDcB18e954cf63BF81A6688bA5E4415E" // paste your own contract address gotten from the terminal

@Injectable()
export class AppService {
  getContractAddress(): string {
    return CONTRACT_ADDRESS;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Open your app.controller.ts file, copy and paste the codes below
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/contract-address')
  getContractAddress(): string {
    return this.appService.getContractAddress();
  }
}

Enter fullscreen mode Exit fullscreen mode
  • Refresh your swagger UI tab in your browser. You'll see the route to get contract address. Execute it and you will get 200 status response like the image below

Image description

  • Next up, we will be using the ethers library that we installed so we can interact with the contract address.
  • First up, we will get the total supply which is a default method generated from an ERC20 token.

  • In your app.controller.ts file, add a new route called /total-suppy, which will be an async operation, returning a promise. The updated file will look like the codes below

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/contract-address')
  getContractAddress(): string {
    return this.appService.getContractAddress();
  }

  @Get('/total-supply')
  async getTotalSupply(): Promise<number> {
    return this.appService.getTotalSupply();
  }
}

Enter fullscreen mode Exit fullscreen mode
  • Next up, in your app.service.ts, we will be creating an asynchronous function called getTotalSupply. We will be using a package called celo-ethers-wrapper to be able to make ethers.js to be compatible with the celo network. In your backend directory of your project, install the package using yarn add @celo-tools/celo-ethers-wrapper. Update your app.service.ts with the codes below
import { Injectable } from '@nestjs/common';
import { ethers } from 'ethers';
import * as MyTokenJson from './assets/MyToken.json';
import { CeloProvider } from '@celo-tools/celo-ethers-wrapper'

const CONTRACT_ADDRESS = "0xBEc9321DEDcB18e954cf63BF81A6688bA5E4415E" // paste your own contract address gotten from the terminal

@Injectable()
export class AppService {
  getContractAddress(): string {
    return CONTRACT_ADDRESS;
  }

  async getTotalSupply(): Promise<number> {
    // Connecting to Alfajores testnet
    const provider = new CeloProvider('https://alfajores-forno.celo-testnet.org')
    await provider.ready
    const contract = new ethers.Contract(CONTRACT_ADDRESS, MyTokenJson.abi, provider );
    return contract.totalSupply();
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Refresh your swagger UI tab on your browser. After executing your /total-supply route, you should get an output like this

Image description
The return value we get is a Big Number. How do we convert it to a string?
Update your getTotalSupply async function with the lines of code below

const totalSupplyBigNumber = await contract.totalSupply();
    const totalSupplyString = ethers.utils.formatEther(totalSupplyBigNumber);
    const totalSupplyNumber = parseFloat(totalSupplyString);
    return totalSupplyNumber;
Enter fullscreen mode Exit fullscreen mode
  • Head to the swagger UI tab in browser, refresh the page and execute the total supply route. You'll get a return value of 0 at the response body because we have not minted any token on our contract.

Image description

  • Minting tokens requires a signer to sign the transaction and that signer has to be the owner or the deployer of the contract. We will use the celoWallet from the celo-ethers-wrapper to be able to sign transactions and make calls. To mint tokens in the backend, we will need to make a POST request. In the app.controller.ts file, we need to first import Post and create a new async method route called mintTokens. Your updated file should look like that
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';


@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}m

  @Get('/contract-address')
  getContractAddress(): string {
    return this.appService.getContractAddress();
  }

  @Get('/total-supply')
  async getTotalSupply(): Promise<number> {
    return this.appService.getTotalSupply();
  }

  @Post('/mint-tokens')
  async mintTokens(){
    return this.appService.mintTokens();
  }
}

Enter fullscreen mode Exit fullscreen mode
  • In the app.service.ts file, import the CeloWallet from celo-ethers-wrapper and create an .env file in the backend directory which stores the private key of the account you used to deploy the smart contract because that is the signer account. Make sure you install dotenv with yarn add dotenv. Your updated file should look like this
import { Injectable } from '@nestjs/common';
import { ethers } from 'ethers';
import * as MyTokenJson from './assets/MyToken.json';
import { CeloProvider } from '@celo-tools/celo-ethers-wrapper'
import { CeloWallet } from "@celo-tools/celo-ethers-wrapper";
import * as dotenv from 'dotenv';
dotenv.config()


const CONTRACT_ADDRESS = "0xBEc9321DEDcB18e954cf63BF81A6688bA5E4415E" // paste your own contract address gotten from the terminal
const WALLET_ADDRESS = "0x122851EB3915cc769dECBf95a566e7fC8aAc2125" // paste your own wallet address

@Injectable()
export class AppService {
  getContractAddress(): string {
    return CONTRACT_ADDRESS;
  }

  async getTotalSupply(): Promise<number> {
    // Connecting to Alfajores testnet
    const provider = new CeloProvider('https://alfajores-forno.celo-testnet.org')
    await provider.ready
    const contract = new ethers.Contract(CONTRACT_ADDRESS, MyTokenJson.abi, provider );
    const totalSupplyBigNumber = await contract.totalSupply();
    const totalSupplyString = ethers.utils.formatEther(totalSupplyBigNumber);
    const totalSupplyNumber = parseFloat(totalSupplyString);
    return totalSupplyNumber;
  }

  async mintTokens(){
    // Connecting to Alfajores testnet
    const provider = new CeloProvider('https://alfajores-forno.celo-testnet.org')
    await provider.ready
    const wallet = new CeloWallet(process.env.PRIVATE_KEY, provider);
    const contract = new ethers.Contract(CONTRACT_ADDRESS, MyTokenJson.abi, wallet );
    const mint = await contract.mint(WALLET_ADDRESS, 100000); 
    return mint;
  }
}
Enter fullscreen mode Exit fullscreen mode
  • In your swagger UI tab, refresh it and execute the mintTokens route which is a POST request. You'll get an output like this

Image description

This indicates that the POST request was successful and your tokens are minted based on the parameter you supplied in your mintFunction method.

  • Next up, let's check the number of tokens we have by executing the /total-supply GET request. You should get an output like this base on the amount of times you execute the mintTokens POST request.

Image description

In my response body, I got the value of 3e+13 which is the E- notation version of 3*10^13.

You can view the details of your contract by searching up the contract address in the celo alfajores explorer

This is the transaction details from my contract address 0xbec9321dedcb18e954cf63bf81a6688ba5e4415e.

  • This is the end of this tutorial and I hope you learned one or two from this tutorial and it helped in one way or the other. Let me know what you think. Cheers

Top comments (0)