Building on Linea: ERC20 & Tokens

Blockchain and web3 have changed the way we think about our digital assets and the way we transfer value. One revolutionary primitive is the digital token, a programmable asset that can represent anything from physical assets to  voting rights. Tokens enable complex interactions  and a rich roster of functions through decentralized applications (dapps)  built on networks like Ethereum and Linea.

However, during the early Ethereum days there were no standard ways to implement tokens. Each and every project had its own unique implementation, which made it difficult for exchanges, wallets and dapps to support all types of tokens; this lack of compatibility also created difficulties in terms of growth and adoption.

To solve this issue, the Ethereum community proposed the EIP20 (Ethereum Improvement Proposal) standard in 2015. ERC20 (Ethereum Requests for Comment 20) defined a common interface and some set of rules for fungible tokens, which is mostly tokens where each unit is exactly equivalent to any other unit. By using this standard, token projects could ensure compatibility with the broader Ethereum ecosystem with very few adjustments.

Today ERC20 has become the standard for the fungible tokens not just on Ethereum but also on compatible blockchains like Linea zkEVM as well. Most of the tokens that you interact with in the web3 ecosystem – from stablecoins to governance tokens or utility tokens – are most likely ERC20 under the hood.

In the rest of this post, we’ll dive deeper into the specification of ERC20 and walk through the implementation using OpenZeppelin library and deploying your tokens on Linea. By the end, you will be deploying your own token on Linea while having a solid understanding of tokens.

For more, also check out our earlier Getting Started Guide on Deploying a Smart Contract.

Why Deploy ERC20 Tokens on Linea?

Linea, a zkEVM (zero-knowledge Ethereum Virtual Machine) launched by Consensys in 2023, offers an exciting solution regarding scalability challenges faced by Ethereum. As a type 2 zkEVM, Linea maintains compatibility with existing Ethereum infrastructure while improving transaction speed and reducing costs. This compatibility helps developers to easily migrate dapps and tokens to Linea without making any changes to their codebases.

By leveraging Linea's advanced cryptographic techniques, such as SNARKs (Succinct Non-Interactive Argument of Knowledge), transactions are processed off-chain and verified on the Ethereum network using compact proofs. This approach not only reduces gas fees and confirmation times but also ensures the security and decentralization principles that are central to Ethereum.

The ERC20 Standard

At the core of the ERC20 standard defines six functions and two events. By implementing these functions and events, a token contract can be considered ERC20 and can interact seamlessly with other contracts and applications that support the ERC20 standard.

Functions are:

totalSupply() , balanceOf(address account) , transfer(address recipient, uint256 amount), allowance(address owner, address spender) , approve(address spender, uint256 amount) and transferFrom(address sender, address recipient, uint256 amount)

And the two events are:

Transfer(address indexed from, address indexed to, uint256 value) and Approval(address indexed owner, address indexed spender, uint256 value) .

Creating Your Own Token

While this basic implementation is functional, it's generally recommended to use battle-tested, audited implementations like those provided by the OpenZeppelin library.

Let’s create a simple Token called RadToken using Foundry.

To get started, let's install Foundry toolchain installer.

curl -L https://foundry.paradigm.xyz | bash

Now we will install foundry by running the following command below. 👇

foundryup

We finally installed Foundry. 🎉

Now we can use the CLI tools from Foundry. To verify if Foundry was truly installed we can run forge --help .

Get Started ✨

We need to initialize a new project.

forge init linea-erc20

It will create few files and folders.

Let's take a look at the folder structure to understand what each of them are for:

  • src: default directory where we will mostly write and create the smart contracts

  • lib: contains dependencies which are mostly helpful contracts to use with foundry

  • script: contains examples of solidity scripting files

  • tests: this is the default directory that contains an example test

Writing the Small ERC20 Contract

Let’s create a simple basic ERC20 contract and a deploy script called RadToken.sol and DeployRadToken.s.sol .

First, let's take a look at Radtoken.sol . This contract, named RadToken, is an ERC20 token contract. It imports the ERC20 contract from the OpenZeppelin library, which provides a standard implementation of the ERC20 token standard.

The RadToken contract inherits from the ERC20 contract using the is keyword. This means that RadToken will have all the functionality of an ERC20 token.

// RadToken.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract RadToken is ERC20 {
constructor(uint256 initialSupply) ERC20("RadToken", "RAD") {_
mint(msg.sender, initialSupply);  
}
}

The contract has a constructor function that takes a parameter initialSupply of type uint256. This parameter represents the initial supply of tokens when the contract is deployed. Inside the constructor, two things happen:

  1. The ERC20 constructor is called with the arguments "RadToken" and "RAD", which set the name and symbol of the token, respectively.

  2. The _mint function is called, which mints initialSupply tokens and assigns them to the address that deploys the contract (msg.sender).

By inheriting from ERC20, the RadToken contract automatically inherits all the standard ERC20 functions and events, such as totalSupply(), balanceOf(), transfer(), approve(), allowance(), transferFrom(), and the Transfer and Approval events.

Also here’s DeployRadToken.s.sol :

This contract, DeployRadToken, is a script contract used for deploying the RadToken contract. It imports the necessary contracts and libraries:

  • Script and console2 from "forge-std/Script.sol", which provide utilities for writing deployment scripts in Forge.

  • RadToken from "../src/RadToken.sol", which is the contract we want to deploy.

The DeployRadToken contract inherits from the Script contract, indicating that it is a deployment script.

// SPDX-License-Identifier: MITpragma solidity ^0.8.24;import {Script, console2} from "forge-std/Script.sol";
import {RadToken} from "../src/RadToken.sol";

contract DeployRadToken is Script {
    uint256 public constant INITIAL_SUPPLY = 1_000_000 ether; 

    function run() external returns (RadToken) {
        vm.startBroadcast();
        RadToken radToken = new RadToken(INITIAL_SUPPLY);
        vm.stopBroadcast();
        return radToken;
    }
}

Inside the contract, a constant variable INITIAL_SUPPLY is defined with a value of 1_000_000 ether. This represents 1 million tokens with 18 decimal places (the default for most ERC20 tokens). The ether keyword is used as a unit of measurement for the token supply.

The run function is an external function that returns an instance of the deployed RadToken contract. It is the entry point for the deployment script. Here's what happens inside the run function:

  1. vm.startBroadcast() is called to start the broadcasting of transactions.

  2. A new instance of the RadToken contract is created using new RadToken(INITIAL_SUPPLY), passing the INITIAL_SUPPLY as an argument to the constructor.

  3. vm.stopBroadcast() is called to stop the broadcasting of transactions.

  4. The deployed RadToken instance is returned.

This script contract can be run to deploy the RadToken contract with an initial supply of 1 million tokens.

In summary, RadToken.sol defines an ERC20 token contract, and DeployRadToken.s.sol is a script contract used to deploy the RadToken contract with a specified initial supply.

Now that we wrote a contract we can run a build and compile ABIs by running forge build

We will notice that it created an out directory for the ABIs for the smart contracts and will also create a cache folder.

Getting Started with Hardhat

To deploy the contract with Hardhat, we’ll need to initiate a Hardhat project. To do that we’ll have to do the following steps.

mkdir linea-erc20-hardhat
cd linea-erc20-hardhat
npx hardhat init

We’ll see the following:

$ npx hardhat init
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

👷 Welcome to Hardhat v2.22.15 👷‍

? What do you want to do? …  
Create a JavaScript project
❯ Create a TypeScript project
  Create a TypeScript project (with Viem)
  Create an empty hardhat.config.js
  Quit

We’ll pick the Typescript project. Now we’ll have some files, but we need to head over to hardhat.config.ts file and add the following:

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

dotenv.config();

const config: HardhatUserConfig = {
  solidity: "0.8.24",
  networks: {
    "linea-testnet": {
      url: `https://linea-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
      accounts: [process.env.ACCOUNT_PRIVATE_KEY!],
    },
  },
};

export default config;

Installing OpenZeppelin Library

We will need to install OpenZeppelin library for our ERC20 smart contract as we’re using the library

for Hardhat, we’ll use this command:

npm install @openzeppelin/contracts

for Foundry we’ll use this command:

forge install Openzeppelin/openzeppelin-contracts

Deploying on Linea

Now that our ERC20 contract is ready, it’s time for us to deploy the smart contract on the Linea network. To deploy the smart contract on Linea we will need a few things. We will need an Infura endpoint url and API key, some LineaSepolia testnet tokens? and the private key of your MetaMask wallet.

With Hardhat:

To deploy the contract with Hardhat we’ll need to write a Hardhat ignition module called RadToken.sol , here’s how it would look like:

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

// Default initial supply: 1 million tokens with 18 decimals
const DEFAULT_INITIAL_SUPPLY: bigint = 1_000_000n * 10n ** 18n;

const RadTokenModule = buildModule("RadTokenModule", (m) => {
  // Allow the initial supply to be configurable, with a default value
  const initialSupply = m.getParameter("initialSupply", DEFAULT_INITIAL_SUPPLY);

  // Deploy the RadToken contract
  const radToken = m.contract("RadToken", [initialSupply]);

  return { radToken };
});

export default RadTokenModule;

Now we’ll run npx hardhat compile to compile the contract.

We’ll need to create a .env file and add the following environment variable like Infura API key and MetaMask wallet private key.

To deploy the contract we’ll use the following command on the terminal:

npx hardhat ignition deploy ignition/modules/RadToken.ts --network linea-testnet

Once deployed we’ll see the following on the terminal:

With Foundry:

Now to deploy the contract with Foundry we don’t need to write any specific deploy script again, we already wrote RadToken.s.sol . We will use the following command.

$ forge script script/DeployRadToken.s.sol:DeployRadToken --rpc-url <https://linea-sepolia.infura.io/v3/><API-KEY> --private-key <your_MetaMask_private_key> --broadcast

But it’s not the best practice to paste your private key and api key on the terminal. The best way to go about it is to create a .env file and add the following:

PRIVATE_KEY=your metamask private key here
INFURA_API_KEY=your infura api key here

And on the foundry.toml file add the following:

[rpc_endpoints]
linea-sepolia = "https://linea-sepolia.infura.io/v3/${INFURA_API_KEY}"

Now on the terminal we can run this command:

forge script script/DeployRadToken.s.sol:DeployRadToken --rpc-url linea-sepolia --private-key $PRIVATE_KEY

In this way we’re not pasting sensitive information on the terminal.

We’ll see the following on our terminal:

We’ve successfully deployed our ERC20 contract on Linea Sepolia Testnet.

You can find the whole Foundry code here: https://github.com/meowyx/linea-erc20

And the Hardhat one here: https://github.com/meowyx/linea-erc20-hardhat

ERC20 tokens are an important part of web3 and decentralized web applications from decentralized finance to gaming. By mastering the art of implementing and deploying ERC20 tokens on scalable platforms like Linea, developers are empowered to build the next generation of innovative, user-friendly, and cost-effective dapps.

Whether you choose to write your ERC20 implementation from scratch or leverage audited libraries like OpenZeppelin, deploying your tokens on Linea is the first step forward. Maybe experiment and create some more exciting use-cases. There are also the ERC721 standard and ERC-1155 to look forward to and experiment with!

Happy Coding! 🚀

For more, also check out our earlier Getting Started Guide on Deploying a Smart Contract.

Subscribe to Linea
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.