Create & deploy an ERC-20 token in 15 minutes (Truffle, OpenZeppelin, Goerli)
Tags: solidity, javascript, truffle, web3
Published: 2022-11-02 19:56:50

_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](https://blog.ethereum.org/2022/06/21/testnet-deprecation)). So here's an updated take, written in November 2022..._

Here's what we'll be doing:

- Leverage [OpenZeppelin](https://www.openzeppelin.com/)'s secure & community reviewed contracts to create an ERC-20 token.
- Use [Truffle](https://trufflesuite.com/)'s suite of tools to compile & deploy the contract to an Ethereum network locally, then interact with various methods.
- Utilise [Infura](https://infura.io/)'s Web3 infrastructure platform to deploy the contract to the [Goerli](https://goerli.net/) testnet.
- Find the newly deployed token on [Etherscan](https://etherscan.io/) & import the tokens into [MetaMask](https://metamask.io/).


### 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](https://docs.soliditylang.org/en/v0.8.17/), which is the object-oriented language for implementing smart contracts we'll be using.

Within the `MyToken.sol` add the following:

**`MyToken.sol`**
```js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
```

- The first line signifies which license identifier the contract uses. SPDX license identifier's were introduced in [v0.6.8](https://forum.openzeppelin.com/t/solidity-0-6-8-introduces-spdx-license-identifiers/2859).
- `pragma` is a directive that specifies which compiler version the file should use.
- The import line is importing the contents of [this file](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) which is available within our `node_modules` from installing OpenZeppelin earlier.

Now, time to create the contract:


**`MyToken.sol`**
```js
// ...

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);
    }
}
```

Some notes on the above:

- The contract is [inheriting](https://docs.soliditylang.org/en/v0.8.16/contracts.html#inheritance) 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`**
```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",
		},
	},
};
```

Next up:
1) Install the [HDWalletProvider](https://www.npmjs.com/package/@truffle/hdwallet-provider):
`npm install @truffle/hdwallet-provider`.

2) Install [dotenv](https://github.com/motdotla/dotenv):
`npm install dotenv`.

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

```sh
MNEMONIC=
PROJECT_ID=
```

_(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](https://metamask.zendesk.com/hc/en-us/articles/360015290032-How-to-reveal-your-Secret-Recovery-Phrase) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8nc1geuv6fp8x4pk5gqt.png)

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`**
```js
const MyToken = artifacts.require("MyToken");

module.exports = (deployer) => {
	deployer.deploy(MyToken, "MyToken", "MYT", 100000);
};
```

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](https://trufflesuite.com/docs/truffle/how-to/contracts/run-migrations/#deployer-api). 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:
```sh
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)>
```

> 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: 
```sh
truffle(develop)> name = await token.name();
'MyToken'
truffle(develop)> symbol = await token.symbol();
'MYT'
truffle(develop)> decimals = (await token.decimals()).toString()
'18'
```

_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb23ujajuwmsq87q5g8f.png)

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:

```sh
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
```

#### 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](https://goerli.etherscan.io/token/0x904609375980165691D587386A0163aa7d8D00A6) to view the created contract!


![Etherscan MyToken page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0001stfusy0r48362x7f.png)


#### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87pmh7bi2ghvil85b8y0.png)

and upon clicking "Add custom token"...

![MetaMask screenshot of balance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0c2n30m7f95wdbn621yf.png)

you should be able to see your balance! 🪄

That's it! 🏁

If you're interested in learning more about Solidity, [Crypto Zombies](https://cryptozombies.io/) is a great place to start. [Solidity by example](https://docs.soliditylang.org/en/v0.8.17/solidity-by-example.html) also have good digestible real world examples of where smart contracts can go.