DEV Community

Cover image for Create & deploy an ERC-20 token in 15 minutes (Truffle, OpenZeppelin, Goerli)
Will
Will

Posted on

Create & deploy an ERC-20 token in 15 minutes (Truffle, OpenZeppelin, Goerli)

The Web3 space moves fast! That's great, however, it means most of the guides out there on how to create ERC-20 tokens are using outdated versions and using deprecated features (eg. the Rinkeby testnet). So here's an updated take, written in November 2022...

Here's what we'll be doing:

  • Leverage OpenZeppelin's secure & community reviewed contracts to create an ERC-20 token.
  • Use Truffle's suite of tools to compile & deploy the contract to an Ethereum network locally, then interact with various methods.
  • Utilise Infura's Web3 infrastructure platform to deploy the contract to the Goerli testnet.
  • Find the newly deployed token on Etherscan & import the tokens into MetaMask.

Project Setup

Prerequisite: Install Truffle (as of writing this I am using truffle@5.6.3):
npm install -g truffle

Note: For the sake of this demo I'll be calling the token MyToken, so whenever I reference MyToken, replace it with whatever you want your token name to be.

1) Initialise a bare project template & cd into it:
truffle init MyToken && cd MyToken

2) Install OpenZeppelin so that we can leverage their smart contracts:
npm install @openzeppelin/contracts

3) Open the project in your editor of choice (likely code . if you're using VSCode, use whatever shortcut you have set, or open it manually)

Creating the Token

Take a moment to look at the project structure and you'll notice the following:

contracts/: Directory for Solidity contracts
migrations/: Directory for scriptable deployment files
test/: Directory for test files for testing your application and contracts
truffle.js: Truffle configuration file

Create a file within contracts/ called MyToken.sol. The .sol extension signifies Solidity, which is the object-oriented language for implementing smart contracts we'll be using.

Within the MyToken.sol add the following:

MyToken.sol

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Enter fullscreen mode Exit fullscreen mode
  • The first line signifies which license identifier the contract uses. SPDX license identifier's were introduced in v0.6.8.
  • pragma is a directive that specifies which compiler version the file should use.
  • The import line is importing the contents of this file which is available within our node_modules from installing OpenZeppelin earlier.

Now, time to create the contract:

MyToken.sol

// ...

contract MyToken is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint initialSupply
    ) ERC20(name, symbol) {
        require(initialSupply > 0, "Initial supply has to be greater than 0");
        _mint(msg.sender, initialSupply * 10**18);
    }
}
Enter fullscreen mode Exit fullscreen mode

Some notes on the above:

  • The contract is inheriting from the OpenZeppelin ERC20 contract with the is keyword.
  • The constructor is taking in the name, symbol, and initialSupply which we'll be passing in from the deployer later.
  • Following a quick validation using require, we are using the _mint function which has been inherited from OpenZeppelin to issue the tokens.
  • The initialSupply * 10**18 is representing the balance with 18 decimals. Why? Because decimals are not supported by Solidity and the EVM. In the same way 1 ETH is represented by 10^18 of its natural unit (1 Ether = 1,000,000,000,000,000,000 Wei), we'll be doing the same for our token. This allows us to send arbitrary amounts (eg. 0.0004 MyToken's).

Configuring Truffle

In a newly created Truffle project, there is lots of boilerplate to help you get going, you can reduce it down to this:

truffle-config.js

require("dotenv").config();
const { MNEMONIC, PROJECT_ID } = process.env;

const HDWalletProvider = require("@truffle/hdwallet-provider");

module.exports = {
    networks: {
        development: {
            host: "127.0.0.1", // Localhost (default: none)
            port: 9545, // Standard Ethereum port (default: none)
            network_id: "*", // Any network (default: none)
        },
        goerli: {
            provider: () =>
                new HDWalletProvider(
                    MNEMONIC,
                    `https://goerli.infura.io/v3/${PROJECT_ID}`,
                ),
            network_id: 5, // Goerli's id
            confirmations: 2, // # of confirmations to wait between deployments. (default: 0)
            timeoutBlocks: 200, // # of blocks before a deployment times out  (minimum/default: 50)
            skipDryRun: true, // Skip dry run before migrations? (default: false for public nets )
        },
    },
    compilers: {
        solc: {
            version: "0.8.17",
        },
    },
};
Enter fullscreen mode Exit fullscreen mode

Next up:
1) Install the HDWalletProvider:
npm install @truffle/hdwallet-provider.

2) Install dotenv:
npm install dotenv.

3) Create a .env file at the root of the project directory with the following contents:

MNEMONIC=
PROJECT_ID=
Enter fullscreen mode Exit fullscreen mode

(Don't forget to add the .env file to your .gitignore!)

Time to populate the MNEMONIC & PROJECT_ID values...

Getting the MNEMONIC from MetaMask

Prerequisite: Install the MetaMask browser from https://metamask.io/download/ if you haven't already got it.

1) Follow these instructions to reveal your secret recovery phrase. Note: Do not share this key with anyone or store it online anywhere.

2) Copy the value over to the corresponding MNEMONIC key in the .env file.

Getting the PROJECT_ID from Infura

1) Sign up at https://infura.io/ (it's free!)

2) Once confirmed and logged in, click the "CREATE NEW KEY" button in the top right of the dashboard. Enter a Name and select Web3 API as a Network and then create.

3) Select Görli under Network Endpoints:
Infura screenshot

4) Copy the ID which is in the HTTPS URL over to the corresponding PROJECT_ID key in the .env file.

Creating the migration

To deploy the contract you'll need to create a file in the migrations/ folder:

1_initial_migration.js

const MyToken = artifacts.require("MyToken");

module.exports = (deployer) => {
    deployer.deploy(MyToken, "MyToken", "MYT", 100000);
};
Enter fullscreen mode Exit fullscreen mode

Some notes on the above:

  • We tell Truffle which contracts we'd like to interact with via the artifacts.require() method. The name should match the name of the contract definition within that source file, not the source file (as files can contain more than one contract).
  • The deployer API (deployer.deploy(contract, args..., options)) can be found here. We are passing in the contract, followed by the optional constructor arguments.
  • The constructor arguments are "MyToken" (the name), "MYT" (the symbol), and 100000 (the initial supply).

That should be it for configuration! ⚙️

Deploying the contract locally

To deploy the smart contract, we're going to need to connect to a blockchain. Truffle has a built-in personal blockchain that can be used for testing. This blockchain is local to your system and does not interact with the main Ethereum network.

1) Open a terminal window and cd into your Truffle project directory.

2) Run truffle develop which will start a local Ethereum network.

3) Run compile which will compile the Solidity contract files, which should give you something like this:

truffle(develop)> compile

Compiling your contracts...
===========================
> Compiling ./contracts/MyToken.sol
> Compiling @openzeppelin/contracts/token/ERC20/ERC20.sol
> Compiling @openzeppelin/contracts/token/ERC20/IERC20.sol
> Compiling @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol
> Compiling @openzeppelin/contracts/utils/Context.sol
> Artifacts written to /Users/ME/MyToken/build/contracts
> Compiled successfully using:
   - solc: 0.8.17+commit.8d345f5f.Emscripten.clang
truffle(develop)>
Enter fullscreen mode Exit fullscreen mode

Note: As indicated by the success message, artifacts have been created within your /build/contracts directory. The name of the generated artifact .json files do not reflect the name of the source file but the name of the contract definition.

5) Finally, run migrate --reset to deploy your contract to the locally running network.

--reset runs all migrations from the beginning, instead of running from the last completed migration

Interacting with the token

Time to do some basic checks to see if everything's worked as expected:

1) Run token = await MyToken.deployed(); to access the TruffleContract. This should also output the full contract instance with all available methods, properties, etc.

2) From here, we can test out a few methods:

truffle(develop)> name = await token.name();
'MyToken'
truffle(develop)> symbol = await token.symbol();
'MYT'
truffle(develop)> decimals = (await token.decimals()).toString()
'18'
Enter fullscreen mode Exit fullscreen mode

I encourage you to experiment with other available methods!

Deploying the contract to the Goerli testnet

Before you can deploy the contract to Goerli, we'll need some testnet Ethers! First of all, switch over to the Goerli network on your MetaMask (if it isn't an option for you, you may need to turn on the "Show test networks" option in the advanced settings):

MetaMask screenshot

Then, head over to https://goerlifaucet.com, input your address and hit the "Send me ETH" button. Once the transaction has been confirmed, check your MetaMask to confirm the ETH has arrived, if not - try another faucet.

Time to migrate! Run migrate --reset --network goerli, which should output something like this:

truffle(develop)> migrate --reset --network goerli

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.


Starting migrations...
======================
> Network name:    'goerli'
> Network id:      5
> Block gas limit: 30000000 (0x1c9c380)


1_initial_migration.js
======================

   Deploying 'MyToken'
   -------------------
   > transaction hash:    0x31b69373bdfdabb20e205746150d5c4845b363e9a3755450d23adbad1a736c04
   > Blocks: 1            Seconds: 4
   > contract address:    0x904609375980165691D587386A0163aa7d8D00A6
   > block number:        7875096
   > block timestamp:     1667350704
   > account:             0xEb390e921A349e2434871D989c9AD74bB8de10c0
   > balance:             0.047005278846168288
   > gas used:            1186096 (0x121930)
   > gas price:           2.524855622 gwei
   > value sent:          0 ETH
   > total cost:          0.002994721153831712 ETH

   Pausing for 2 confirmations...

   -------------------------------
   > confirmation number: 1 (block: 7875097)
   > confirmation number: 2 (block: 7875098)
   > Saving artifacts
   -------------------------------------
   > Total cost:     0.002994721153831712 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.002994721153831712 ETH
Enter fullscreen mode Exit fullscreen mode

Viewing the contract on Etherscan

In my case, you'll see as part of the migration output that the contract address is: 0x904609375980165691D587386A0163aa7d8D00A6. Using this, we can go to Etherscan to view the created contract!

Etherscan MyToken page

Adding the token to MetaMask

Using the same contract address, head back to MetaMask and click on "Import token". Upon entering the contract address the symbol and decimals should auto-populate:

MetaMask screenshot of form

and upon clicking "Add custom token"...

MetaMask screenshot of balance

you should be able to see your balance! 🪄

That's it! 🏁

If you're interested in learning more about Solidity, Crypto Zombies is a great place to start. Solidity by example also have good digestible real world examples of where smart contracts can go.

Top comments (0)