How to Create and Deploy an ERC20 Token on Klaytn

How to Create and Deploy an ERC20 Token on Klaytn

In this guide, we shall explore how to create and deploy our very first token to the Baobab network with Klaytn IDE and Kaikas Wallet

·

16 min read

Introduction

As the rise of blockchain applications increases and the solution built around decentralized finance(Defi), NFTs and DAOs, the use of tokens also has increased as they play a major role in most of these platforms. Tokens literally can be used for several different purposes which may vary from on-chain voting, payment of services within specific ecosystems, unit of account and exchange of goods and services etc. It is therefore paramount if you want to build on Klaytn, you must understand ERC-20 compatible token that conforms to the Klaytn Token Standard.

In this guide, we are going to use a step-by-step approach to create and deploy our ERC20 token contract to the Baobab network.

We will first write the smart contract using Klaytn IDE, an online IDE made for Klaytn development with Solidity, and add a few tests, then deploy. Here is a more detailed summary of what we gonna do:

  • ERC20 and KIP 7 (Differences and Similarities)

  • Write our Token contract in solidity with Klaytn IDE.

  • Add a few tests to our smart contract

  • Deploy our smart contract to the Baobab network

  • Interact with our deployed smart contract from Remix

  • Add your custom token to Kaikas Wallet

  • Conclusion

ERC20 and KIP 7

If you're familiar with building on Ethereum or just starting out newly, you must have heard about the popular ERC20 standard. ERC20 represents fungible tokens which means they have the properties of uniformity and divisibility. Every fungible token is interchangeable as each unit of token possesses the same value. Just like every dollar bill has the same value as one dollar.

Literally, ERC-20 tokens have the ability to represent virtually anything. A few examples are lottery tickets, points on an online platform, skills for a character in a game, fiat currency, etc. This feature is quite powerful and needs to be regulated by a standard, and that’s why the ERC-20 exists.

This popularly used standard that runs on Ethereum is extensively used by other blockchains such as Klaytn. This is so because of their EVM compatibility. A brief reminder that Klaytn is an EVM Layer 1 blockchain solution as such this standard literally conforms to the Klaytn Token Standard. However, Klaytn has its standard as regards fungible tokens called the KIP(Klaytn Improvement Proposal) 7. KIP7 is pretty much like ERC20 with a few differences which have been highlighted here

Overview of ERC20 Token Standard Functions

ERC20 standard defines a set of functions to be implemented by all ERC20 tokens so as to allow integration with other contracts, wallets, or marketplaces. This set of functions defines two events and 9 methods (including 3 optional methods) as highlighted below:

function name() public view returns (string) // optional
function symbol() public view returns (string) // optional
function decimals() public view returns (uint8) // optional
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

Given the following interface above, developers may customize tokens by adding new features and logic and deploy on the Klaytn network.

That said, let’s dig straight into how to develop and deploy an ERC20 token on the Klaytn IDE.

Write your Token contract in solidity with Klaytn IDE

We’ll learn how to write an ERC20 Token Smart Contract using Klaytn IDE: an easy-to-use web-based IDE. Navigate toKlaytn IDE, open the contacts folder, and create a new file called “yourFileName.sol”:

Here is the code that you can essentially "Copy/Paste" to create your ERC20 token. Source code from docs.

   pragma solidity ^0.5.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see `ERC20Detailed`.
 */
interface IERC20 {
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
}

library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "SafeMath: modulo by zero");
        return a % b;
    }
}

/**
 * @dev Implementation of the `IERC20` interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using `_mint`.
 * For a generic mechanism see `ERC20Mintable`.
 *
 * *For a detailed writeup see our guide [How to implement supply
 * mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).*
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an `Approval` event is emitted on calls to `transferFrom`.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard `decreaseAllowance` and `increaseAllowance`
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See `IERC20.approve`.
 */
contract MyERC20 is IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    // NOTE Start of https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/token/ERC20/ERC20Detailed.sol
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    constructor (string memory name, string memory symbol, uint8 decimals) public {
        _name = name;
        _symbol = symbol;
        _decimals = decimals;

        _mint(msg.sender, 100000 * 10 ** uint256(decimals)); // CAUTION!
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei.
     *
     * > Note that this information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * `IERC20.balanceOf` and `IERC20.transfer`.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }
    // NOTE End of https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/token/ERC20/ERC20Detailed.sol

    uint256 private _totalSupply;

    /**
     * @dev See `IERC20.totalSupply`.
     */
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See `IERC20.balanceOf`.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See `IERC20.transfer`.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    /**
     * @dev See `IERC20.allowance`.
     */
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See `IERC20.approve`.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    /**
     * @dev See `IERC20.transferFrom`.
     *
     * Emits an `Approval` event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of `ERC20`;
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `value`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to `approve` that can be used as a mitigation for
     * problems described in `IERC20.approve`.
     *
     * Emits an `Approval` event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to `approve` that can be used as a mitigation for
     * problems described in `IERC20.approve`.
     *
     * Emits an `Approval` event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to `transfer`, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a `Transfer` event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount);
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a `Transfer` event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

     /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a `Transfer` event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 value) internal {
        require(account != address(0), "ERC20: burn from the zero address");

    _balances[account] = _balances[account].sub(value);
        _totalSupply = _totalSupply.sub(value);
        emit Transfer(account, address(0), value);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an `Approval` event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

    /**
     * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See `_burn` and `_approve`.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
    }
}

To better understand some of these implementations, learn more here

Let's quickly take a look at some important things

In this guide, our MYERC20 contract contains a constructor which gives us the liberty of setting the details of the token. From the name, symbol, decimal and initial supply of the token.

You can literally customize everything in this area:

So that means, you can customize:

  • The token name

  • The token symbol (I'd go no more than 4 characters here)

  • The token decimal places

  • The amount of tokens to be minted to yourself

Some things to keep in mind:

The amount you're minting to yourself should correlate to the amount of decimal places that you set. For example, if you want a token that has 0 decimal places to have 50 tokens, then the supply would be 50.

But if you have a token with 18 decimal places and you want 50 of them, then the supply would be 50000000000000000000 (18 zeros added to the amount).

You set the number of tokens you receive as the creator of the contract. That’s what this line of code is:

_mint(msg.sender, 50 * 10 ** uint256(decimals))

Whatever you set here will be sent to the wallet of wherever you deployed the contract. We’ll get to that in a few minutes. Once you have all the variables in, it’s now time to test and deploy it to the blockchain.

Add a few tests to our smart contract

Writing test for our contracts is pretty much important as we can’t just deploy our contract and fix it later if a bug is found, that’s not advisable and literally impossible because of the immutability of the blockchain.

Remix offers the possibility to write and run tests. Let’s add a test file so we can ensure that our smart contract works as intended before deploying it.

Come back to the File Explorers and under the tests folder, create a new file named yourFileName_test.sol.

Fill it with this code:

   // SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;
import "../contracts/Erc20.sol";
import "hardhat/console.sol";

contract KlayBank_Test {

    MyERC20 myErc20;

    function beforeAll () public {
        myErc20 = new MyERC20("Emilokan", "EML", 18);
    }

    function mintCheck () public {
        uint totalSupplyBefore = myErc20.totalSupply();
        myErc20.mint(0xF0C566F40Aa7fbe55cF5E3B0ba1d168F3a4ad67F, 100);
        Assert.equal(myErc20.balanceOf(0xF0C566F40Aa7fbe55cF5E3B0ba1d168F3a4ad67F), uint(uint(100)), "MyERC20: balance should be equal amount minted");
        Assert.equal(myErc20.totalSupply(), uint(uint(totalSupplyBefore + 100)), "MyERC20: totalSupply should be equal amount minted");

    }

     function transferToRecipientCheck() public {
        uint recipientBalBefore = myErc20.balanceOf(0x9ae1e982Fc9A9D799e611843CB9154410f11Fe35);
        console.log(recipientBalBefore);
        myErc20.transfer(0x9ae1e982Fc9A9D799e611843CB9154410f11Fe35, 50);
        uint recipientBalAfter = myErc20.balanceOf(0x9ae1e982Fc9A9D799e611843CB9154410f11Fe35);
        console.log(recipientBalAfter);
        Assert.equal(recipientBalBefore + 50, uint(recipientBalAfter), "MyERC20: Recipient balance should increase");
    }




}

Nothing really complex in this file. It’s essentially making sure that when users mint, the balance of the address given is increased and the total supply increases. Also checking for transferring to a recipient: balance of recipient should increase. You can test for as many edge cases as it suites you

Run our test file

In the sidebar, click on the Solidity Unit Testing tab.

Is this tab missing? Click the plugin manager at the bottom of the sidebar. Search for Solidity Unit Testing and activate this plugin.

Select the yourFileName_test.sol file then click on the run button. The tests for our smart contract will run and hopefully pass!

erc20Test.png

Yay, it passed!!💃💃 Now its time to deploy our contract to the Baobab Network

Deploy our smart contract to the Baobab network

Just getting started, you might want to check the necessary steps to undertake: Head up to the deploy section here before deploying. If you have your self set up, then let us deploy!!

Make sure your account has been selected and the contract you want to deploy, and also pass in your constructor arguments as seen below

constructorArgs.png

Click on deploy!!

After a few seconds, your terminal displays a success message like this

tokenCreation.png

To view the transaction on Klaytnscope, copy the address of the already deployed contract and paste it into the block explorer, then click on the transaction hash.

Eml.png

Congratulations 🎉🎉, you’ve just created your first ERC20 token contract on Baobab. Check mine here!

Interact with our deployed token contract from Remix

Let's head back to our IDE. There should now be a deployed contracts panel in the Deploy & Run Transactions tab:

panel.png

Awesome! Let's examine two functions that we created in our token contract: mint, balanceOf

Mint and BalanceOf Function

To mint tokens, we need to fill in the address of the recipient of the token and the amount to be deposited as done below

mint.png

Under the hood, what the mint function does is to create tokens newly from address zero, like a number of tokens, and save it on the blockchain. The effect of this is to increase the balance of the address passed in and the total supply respectively.

balAfterMint.png

tsupply.png

If you notice, the total supply increased by the amount we minted newly.

Add your custom token to Kaikas Wallet

In other to add your tokens to your wallet, you need to readily have your token contract address. In this case, I'll be using the address of Emilokan Token 0x79a0a2917034afe98b7d6bac58447d4a80723c0b

Once that is done, head to your wallet, and click on the token list. This will bring out a tab to add tokens. Click on the add token button, it brings out a tab either to search or add a custom token. Click on the tab of custom tokens and paste your contract address as done below

tkList.png

emilokan.png

emlToken.png

Yay 💃💃 our token has been added to our wallet

Conclusion

Congratulations 🎉🎉 for making it until the end and for writing and deploying your token contract on Klaytn Baobab Network with Klaytn IDE.

Now that you have fully gone through the process, it is important to note that since Klaytn published KIP-7 and as its token standards, it is recommended to implement fungible token contracts according to KIP-7, rather than following ERC-20. KIP-7 is based on ERC-20, but they are tailored for Klaytn and thus more suitable on the Klaytn ecosystem.

This was a very quick guide in creating and deploying your token smart contract on Baobab with Klaytn IDE. Even though we did not really go into some code implementation and all. You can check out solidity docs.

If you have any questions, suggestions or want some Emilokan Tokens you can drop your address in the comment section below, or reach out to me on Twitter!

Stay Buidling 💪💪