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";
- 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);
}
}
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
, andinitialSupply
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",
},
},
};
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=
(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:
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);
};
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), and100000
(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)>
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'
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):
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
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!
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:
and upon clicking "Add custom token"...
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 (1)
Dear will
I am Dr. Paul choo ,korean who live in helsinki,long time ago i and Risto and mike met you at holland.
Do you remember me ,i want to contect you as soon as possible to make a kind of new token .
Could you reply soon
Best regards
Dr. Paul