Tokenization for Everyone: Creating Your Own Token -Fullstack

Parshvi Srivastava
Coinmonks
12 min readApr 2, 2023

--

This is submission for deepdive challenge hosted by Techwriter guild at Alchemy Learn Discord server.

In 2017 something extraordinary happened Bitcoin which is first and most popular cryptocurrency went from $1000 to $20000 within a period of six of months. This put cryptocurrencies in main stream of adoption made it so popular and inspire with this other cryptocurrencies also known as altcoin like — ether, matic,solana, BNB and sometimes refer as shitcoins(no value generated) like Elon Musk’s favourite dogecoin and another PooCoin(yes they exist try google them).

Then in 2021 another something amazing happened a digital artist named Beeple sold an NFT named “Everydays: The First 5000 Days” for 69 million dollar and that created a craze among everyone and everyone wanted to buy NFTs.

All these incidents were very important not only in the history of Crypto but in the history of mankind it provided alternative to high regulated and centralised market to a decentralised and peer-to-peer based market. There is also another common things between these incidents both are tokens which are digitally generated.

Since they digital generated and present in the decentralised network they can be created and look into by anyone who has small coding skills which includes understanding of little bit solidity and Ethereum ecosystem.

But before we get into how to create these Currencies and NFTs let’s understand them. So both currencies and NFTs are tokens. A token is a digital asset that are created by smart contracts(contract created by codes) and once the contract is deployed on the chain using code written they can be transferred or even burned based on requirement.

Based on fungibility tokens can be divided into two parts — Fungible and Non-Fungible. Fungible tokens are digital assets that are interchangeable with each other and have the same value. In other words, each unit of a fungible token is identical to every other unit of the same token, and they can be exchanged with one another on a one-to-one basis. Examples of fungible tokens include cryptocurrencies like Bitcoin and Ethereum. Non fungible tokens are not interchangeable they are unique in properties and have a factor or scarcity. Examples of Non- Fungible Tokens (NFTs) are CryptoPunks, Bored Ape Yacht Club.

Here is the guide to create your own token.

Prerequisite

  1. Node.js version (16.4.0) needs to installed in the system.

2. MetaMask browser extension needs to be installed.

Stack Used

Web application frameworkNext.js
Solidity development environmentHardhat
File StorageIPFS
Ethereum Web Client LibraryEthers.js.

About the project

The project we will be building a smart contract to create our own tokens — fungible and non-fungible and a simple dashboard where you will hold a wizard NFT and power tokens which you can transfer to other wallet address.

To add metadata uri you can upload your files on IPFS using Pinata

The token uri should look some thing like


// Fungible Token
{
"id":1,
"name":"Gold",
"symbol": "GOLD"

}

// Non-Fungible Token
{
"id":2,
"name":"My old wizard",
"description":"it's an old wizard who hold too much gold",
"image":"https://gateway.pinata.cloud/ipfs/QmdBPbkpQQNk7DsjU3cCcuR3QCaBsWcv96pUy8NVuneABz"
}

About Polygon

Project setup

Create your next.js app using the following command in the terminal

npx create-next-app create-your-token --ts

Go inside the folder using cd .

Add dependencies like — hardhat (smart contract development), openzepplin(for secure smart contract development)

npm install hardhat
npm install @openzeppelin/contracts

Setting up Tailwind CSS

We will be using Tailwind for styling the application.

You can install and configure tailwind using the following command

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Add following code to your tailwind.config.js file

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};

Remove all the code present in the global.css file under styles folder

Add the following code.

@tailwind base;
@tailwind components;
@tailwind utilities;

Now tailwind should work in your next.js application.

You can check the by adding the following code to your index.tsx file

import type { NextPage } from 'next'


const Home: NextPage = () => {
return <h1 className="text-3xl font-bold underline">Hello world!</h1>;
}

export default Home

You can run the application using

npm run dev

It should look this

Configuring Hardhat

Now we need to add hardhat environment in our application .

Run the command

npx hardhat

choose typescript option

It may give error due to already present ReadMe and tsconfig file remove them first before running the application.

Once this is done you should see following files and folders in your project.

hardhat.config.js — it has all the configuration related to hardhat .

Scripts — in this folder you should be able to find deploy.ts in which we will deploy our smart contract.

Contracts — A dummy smart contract which and we will create our smart contract in this folder.

test — to write test cases for the smart contracts

We need to configure our hardhat.config.js file for local , testnet, main net environment.

Currently we will add only for hardhat local environment.

Add the following code in your hardhat.config.js file

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
solidity: "0.8.18",
networks: {
hardhat: {
chainId: 1337,
},
},
};

export default config;

We are adding development network in our project

Smart Contract

In this project we will use ERC 1155 which helps us to generate both type of tokens fungible as well as non-fungible.

Delete the pre-existing file in the Contract folder and

Create a new file called MyToken.sol and add the following code.

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

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

contract MyTokens is ERC1155 {
uint256 public constant ELEMENTAL_MANIPULATION = 0;
uint256 public constant MIND_CONTROL = 1;
uint256 public constant TELEKINESIS = 2;
uint256 public constant IMMORTALITY = 3;
uint256 public constant INVISIBILITY = 4;
uint256 public constant WIZARD = 5;
event transferSuccessful(address _transferredTo, uint256 _id, uint256 _amount);
event batchTransferSuccessful(address _transferredTo, uint256[] _id, uint256[] _amount);
constructor() ERC1155("https://gateway.pinata.cloud/ipfs/QmRPHKoMGjG5cchMfquiEwti7vest2xN2D1pM7Up4JcNwz/{id}.json") {
_mint(msg.sender, ELEMENTAL_MANIPULATION, 1000, "");
_mint(msg.sender, MIND_CONTROL, 1000, "");
_mint(msg.sender, TELEKINESIS, 1000, "");
_mint(msg.sender, IMMORTALITY, 1000, "");
_mint(msg.sender, INVISIBILITY, 1000, "");
_mint(msg.sender, WIZARD, 1, "");

}
function transferTokens ( address _from, address _to,uint256 _id, uint256 _amount, string memory _data ) public{
bytes memory data = abi.encode(_data);
_safeTransferFrom(_from,_to,_id,_amount,data);
emit transferSuccessful(_to,_id,_amount);

}
function transferTokensInBatch ( address _from, address _to, uint256[] memory _id, uint256[] memory _amount, string memory _data ) public{
bytes memory data = abi.encode(_data);
_safeBatchTransferFrom(_from,_to,_id,_amount,data);
emit batchTransferSuccessful(_to,_id,_amount);

}
}

We imported ERC1155 from openzepplin library we have installed and is inherited by our smart contract.

We added ids of each components like elemental manipulation’s id is 0, mind control id is 1 we get these ids based on the metadata we have provided.

then in constructor we added the metadata uri as “https://wizardOfCrypto.com/assets/{id}.json" {id} should be replaced with the corresponding ids.

At last we minted all the tokens by providing the wallet address, id, supply and data bytes.

One thing needs to be noticed that supply of powers are in 1000 so they will be fungible tokens means same powers of 1000 will be created and wizard will be only 1 so it will be a NFT.

You can access other functionality like batch minting in which you can mint your tokens in batch, even burning the tokens.

Writing Test Cases

Now we will add test cases to check if our smart contract actually works.

For that you need to first remove the Lock.ts file from your test folder and create a new file called MyTokens.ts.

Add the following code.

import { expect } from "chai";
import { ethers } from "hardhat";

describe("MyToken", function () {
// and reset Hardhat Network to that snapshot in every test.
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,

describe("should create all the tokens", function () {
describe("#mint",()=>{
it("all the tokens should be at the owner address", async () => {
const [owner] = await ethers.getSigners();
const MyTokens = await ethers.getContractFactory("MyTokens");
const myTokens = await MyTokens.deploy();
await myTokens.deployed();
expect(await myTokens.balanceOf(owner.address, 0)).to.equal(1000);
});
});
describe("#ids", () => {
it("should get the ids", async () => {
const MyTokens = await ethers.getContractFactory("MyTokens");
const myTokens = await MyTokens.deploy();
await myTokens.deployed();
expect(await myTokens.WIZARD()).to.equal(5);
});
});
describe("#transfer", () => {
it("owner should be able to transfer the tokens", async () => {
const [owner,otherAddress] = await ethers.getSigners();

const MyTokens = await ethers.getContractFactory("MyTokens");
const myTokens = await MyTokens.deploy();
await myTokens.deployed();
const tokenId =1;
const tokenAmount =10
await myTokens.transferTokens(
owner.address,
otherAddress.address,
tokenId,
tokenAmount,
""
);
expect(await myTokens.balanceOf(otherAddress.address,tokenId)).to.equal(tokenAmount);
});
it("owner should be able to transfer the tokens in batch", async () => {
const [owner, otherAddress] = await ethers.getSigners();

const MyTokens = await ethers.getContractFactory("MyTokens");
const myTokens = await MyTokens.deploy();
await myTokens.deployed();
const tokenIds = [0,1,2];
const tokenAmounts = [10,20,30];
await myTokens.transferTokensInBatch(
owner.address,
otherAddress.address,
tokenIds,
tokenAmounts,
""
);
expect(
await myTokens.balanceOf(otherAddress.address, tokenIds[0])
).to.equal(tokenAmounts[0]);
});
});


});


});

We have added test cases to check the balance of the address and also to tranfer a single token and and batch of tokens.

To run the test cases you need to run the command npx hardhat test

Deploy in Hardhat Local Environment

We can also deploy the smart contract in local hardhat environment by updating the code in deploy.ts file in script folder.

import { ethers } from "hardhat";

async function main() {

const MyTokens = await ethers.getContractFactory("MyTokens");
const myTokens = await MyTokens.deploy();

await myTokens.deployed();

console.log(
`MyTokens smart contract is deployed to ${myTokens.address}`
);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

Run the command in your terminal npx hardhat node.

This will run a local hardhat node in your system. You should be able to see all the mock accounts created and with address and private key.

Add the private key to your metamask and switch to local network you should be able to see your metamask

Now to deploy the contract you need to run the following command in a new terminal npx hardhat run scripts/deploy.ts — network localhost.

You can get the contract address from the terminal and can add it to your env file for connecting to frontend.

Building the front end

Connect Next.js to Smart Contract

First we need to install ethers.js library so that we can connect our metamask wallet to our application using command npm install ethers@5.6.9.

Latest version have different way of connecting to provider.

Create a new file in scripts folder called ethers.ts.

import { ethers } from "ethers";
import MyTokens from "../artifacts/contracts/MyTokens.sol/MyTokens.json";
const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;
declare global {
interface Window {
ethereum?: any;
}
}
export const getAccount = async () => {
await window?.ethereum.request({ method: "eth_requestAccounts" });
const accounts = await window.ethereum.request({ method: "eth_accounts" });
let account = accounts[0].toString();
return account;
};

export const connectContract = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
let contract = null;
if(contractAddress){
contract = new ethers.Contract(contractAddress, MyTokens.abi, signer);

}
return contract;
};

We are getting our abi encode of smartcontract from artifacts folder.

We also got our contract deployed address from what we added to our .env file. You may need dotenv library to get your environment variable. Just run the command npm install dotenv.

Two functions are created first is getAccount which will ask Metamask to connect your account and will return your account address. Make sure your metamask is in local network. Second function will help you to connect with your Smart Contract using already present provider and signer in ethers.js.

Connect your wallet and get the balance of all the powers.

Now we will build the dashboard for checking the balance and transfer the fund.

import type { NextPage } from "next";
import MyForm from "./form";
import { getAccount, connectContract } from "../scripts/ethers";
import { useState } from "react";
import { Contract } from "ethers";
const tokens = [
{ id: 0, name: "Elemental manipulation" },
{ id: 1, name: "Mind control" },
{ id: 2, name: "Telekinesis" },
{ id: 3, name: "Immorality" },
{ id: 4, name: "Invisibility" },
];
interface Powers {
id: number;
name: string;
balance: number;
}
const Home: NextPage = () => {
const [wizard, setWizard] = useState<String | null>(null);
const [powers, setPowers] = useState<Powers[]>();
const handleConnectWallet = async () => {
let account = await getAccount();
setWizard(account);
let _power = [];
const myTokens = await connectContract();
console.log(myTokens);

if (myTokens) {
for (let token of tokens) {
let balance:number = parseInt(await myTokens.balanceOf(account, token.id));
_power.push( { ...token, balance });
}
}
setPowers(_power)

};

return (
<div className="bg-[url(/background.jpeg)] bg-cover bg-no-repeat h-screen text-[#FFFFFF]">
<div className="flex justify-end">
{wizard ? (
<p className="text-right">Account:{wizard.slice(0, 6)} ....</p>
) : (
<button onClick={handleConnectWallet}>Connect Wallet</button>
)}
</div>
<div className=" flex justify-center backdrop-blur-lg bg-white/0.1 w-[70%] h-[70%] mx-auto my-12">
<div className="text-center text-[#001f3f]">
<p className="text-4xl p-4">Welcome Wizard!!</p>
<p className="text-2xl">You can give powers</p>
<div className="flex justify-center">
{powers &&
powers.map((ele) => (
<div key={ele.id} className="m-2 text-purple-600">
<p>{ele?.name}</p>
<p>{ele?.balance}</p>
</div>
))}
</div>
{wizard&&<MyForm />}
</div>
</div>
</div>
);
};

export default Home;

Initially the dashboard should look like this

and when you connect your wallet the end result should look like this.

it should show all the powers and their balance with option to transfer

Now create a new component called form.tsx and add the transfer functionality

import React from "react";
import { useState } from "react";


interface FormData {
address: string;
amount: number
powers: string[];
}


interface MyFormState {
address: string;
amount: number;
powers: string[];
}
const POWERS =["Elemental manipulation", "Mind control", "Telekinesis","Immorality","Invisibility" ];

const MyForm: React.FC = () => {
const [formData, setFormData] = useState<MyFormState>({
address:"",
amount:0,
powers: POWERS
});

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};

return (
<form onSubmit={handleSubmit} className="flex">
<input
type="text"
name="address"
onChange={handleInputChange}
placeholder="Enter the sender address"
className="px-4 py-2 border-gray-300 border rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500 flex-grow"
/>
<input
type="number"
name="amount"
onChange={handleInputChange}
placeholder="Enter the amount"
className="px-4 py-2 border-gray-300 border rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500 flex-grow"
/>
<div className="w-full p-2">
<select
id="interests"
value={formData.powers}
onChange={(event) =>
setFormData({
...formData,
powers: Array.from(
event.target.selectedOptions,
(option) => option.value
),
})
}
>
{POWERS.map((pow, idx) => (
<option value={idx} key={idx}>
{pow}
</option>
))}
</select>
</div>
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white font-semibold rounded-md"
>
Submit
</button>
</form>
);
};

export default MyForm;

On click of singleTransfer button you should be able to see the following screen.

By adding the address and selecting power you can send the power to other address.

Batch transfer does the same thing but you can send multiple powers to same address at the single time.

Deploying to Polygon

To deploy your contract on Polygon testnet you need to first

Create an Alchemy project as it a provider which will provide better connection to block chain and add your API key in your .env file

Also add your private key of the account with which you want to deploy your contract with be very careful with the network connected make sure it is mumbai testnet only You can also connect direct through alchemy dashboard.

Also add some test matic to your account so that you can deploy the contract to the network.

Configuring the network

Add mumbai network in your hardhat.config.ts file

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import { config as dotEnvConfig } from "dotenv";
dotEnvConfig();
let account:string = `0x${process.env.NEXT_PUBLIC_PRIVATE_KEY}`||''

const config: HardhatUserConfig = {
solidity: "0.8.18",
networks: {
hardhat: {
chainId: 1337,
},
mumbai: {
url: `https://polygon-mumbai.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_API_KEY}`,
accounts: [account],
},
},
};

export default config;

Now run the command in the terminal npx hardhat run scripts/deploy.ts — network mumbai

Note down the contract address and update it inside your .env file.

Congrats!! your smart contract is present in polygon testnet and you can verify it on here

Next steps

Before moving forward you must congratulate yourself as you were able to create a very advance full stack decentralised app.

Next step could be creating your own functionality and experimenting as much as possible.

You can read various sources present on Alchemy Platform, Openzeppelin or go to Ethereum official website and read about it.

The gist of demo is here and do show some love at github.

You can find the running demo here.

New to trading? Try crypto trading bots or copy trading on best crypto exchanges

Join Coinmonks Telegram Channel and Youtube Channel get daily Crypto News

Also, Read

--

--

Parshvi Srivastava
Coinmonks

Business Analysts||Web3 Enthusiastic ||Frontend Developer || Navigating Life