In this tutorial we will use a Preset ERC20 contract in OpenZeppelin Contracts v3.x to create an ERC20 using Truffle, without having to write any Solidity code.
Setting up the Environment
We begin by creating a new project.
$ mkdir mytoken && cd mytoken
$ npm init -y
Then we install OpenZeppelin Contracts
$ npm i --save-dev @openzeppelin/contracts
Next we install a development tool for deployment, for this tutorial we will use Truffle but we could use any other tools such as OpenZeppelin CLI.
$ npm i truffle
Getting the contract artifacts
We will setup our Solidity project using truffle init
to create a contracts
directory and configuration to connect to a network.
$ npx truffle init
Starting init...
================
> Copying project files to /home/abcoathup/projects/mytoken
Init successful, sweet!
We are going to use Preset ERC20PresetMinterPauser
which is an ERC20 that is preset so it can be minted, paused and burned.
The Preset contracts have already been compiled, so we only need to copy the artifacts to the build/contracts
directory.
$ mkdir -p build/contracts/
$ cp node_modules/@openzeppelin/contracts/build/contracts/* build/contracts/
Deploy the contract
We will use truffle develop
to open a Truffle console with a development blockchain
$ npx truffle develop
Truffle Develop started at http://127.0.0.1:9545/
Accounts:
(0) 0x0445c33bdce670d57189158b88c0034b579f37ce
(1) 0x46b68a577f95d02d2732cbe93c1809e9ca25b443
(2) 0xff8ddfd4ae8ed56c4b94738fef931b732f3aaeb5
(3) 0xc28aaeb2c63c3617e6733d786a6d7273871b805f
...
Private Keys:
(0) 11e8321617611610d7561dfdf1fdbf87cee4cc99b97cc73c68f0eb5715fff7cc
(1) b28ee7deb262fbb6cccabb43179406e97bf7ff4c20b66feac3321019373531bd
(2) 44e5ce1f204a63e50faf2f202af1d5653aace83805dd9bc7062033f0625aaaa7
(3) 168e28ad21ede684f8fd3ddc8b88e9474c740884f43bda1d567e968544b53a6e
...
Mnemonic: ridge drop soon clutch empty north car drum maximum obey clinic coin
⚠️ Important ⚠️ : This mnemonic was created for you by Truffle. It is not secure.
Ensure you do not use it on production blockchains, or else you risk losing funds.
truffle(develop)>
We can deploy our new token to our development blockchain. Providing a name and a symbol as parameters to the constructor to create a new ERC20PresetMinterPauser
.
truffle(develop)> token = await ERC20PresetMinterPauser.new("My Token", "TKN")
undefined
Interact with our Token
The accounts that we can use were displayed when we started truffle develop
Token metadata
We can call the contract to read token metadata such as name
, symbol
and decimals
truffle(develop)> await token.name()
'My Token'
truffle(develop)> await token.symbol()
'TKN'
truffle(develop)> (await token.decimals()).toString()
'18'
Mint
We can send a transaction to mint tokens to a given account, from an account with the minter role.
In our case we are minting from the account which deployed the token, which is given the minter role.
We will mint 1000 tokens (the token has 18 decimals).
truffle(develop)> await token.mint('0x46b68a577f95d02d2732cbe93c1809e9ca25b443','1000000000000000000000')
{
tx: '0xf04c4342eadd64e81ba631d7340ca1bec4f402f2775e8a9087c8af06ff34e7c7',
receipt: {
transactionHash: '0xf04c4342eadd64e81ba631d7340ca1bec4f402f2775e8a9087c8af06ff34e7c7',
transactionIndex: 0,
blockHash: '0x907fc78d3973dc1b3081148c2ab3172857f32ffc73d6102b756033f60431c850',
blockNumber: 2,
from: '0x0445c33bdce670d57189158b88c0034b579f37ce',
to: '0x3797c825cac4a1fa765f6d8cd7787fb195849555',
gasUsed: 68253,
...
We can check the balance
truffle(develop)> (await token.balanceOf('0x46b68a577f95d02d2732cbe93c1809e9ca25b443')).toString()
'1000000000000000000000'
Burn
We can send a transaction to burn tokens (from the account holder)
truffle(develop)> await token.burn('10000000000000000000', {from: '0x46b68a577f95d02d2732cbe93c1809e9ca25b443'})
{
tx: '0x626b3c012f8edb66cbb45b279554c8f0ac9f5db35097e1364a45e9df5e96027e',
receipt: {
transactionHash: '0x626b3c012f8edb66cbb45b279554c8f0ac9f5db35097e1364a45e9df5e96027e',
transactionIndex: 0,
blockHash: '0x73ec9a1245d2d28cf08aaefb63975542c8c23ee221ef3479a26e00f5fbc7b949',
blockNumber: 3,
from: '0x46b68a577f95d02d2732cbe93c1809e9ca25b443',
to: '0x3797c825cac4a1fa765f6d8cd7787fb195849555',
gasUsed: 36797,
...
We can check the balance after burning
truffle(develop)> (await token.balanceOf('0x46b68a577f95d02d2732cbe93c1809e9ca25b443')).toString()
'990000000000000000000'
Pause
We can pause our Token by sending a transaction calling the pause
function, from an account with the pauser role.
In our case we are pausing from the account which deployed the token, which is given the pauser role.
truffle(develop)> await token.pause()
{
tx: '0x93385cbb2d893658878db962cce2fcc520e211cfdc1a9d49660489b8d20b6768',
receipt: {
transactionHash: '0x93385cbb2d893658878db962cce2fcc520e211cfdc1a9d49660489b8d20b6768',
transactionIndex: 0,
blockHash: '0x84529e46656d47dd642b10ed3606b7306de8020060d7711dc5a963d6cb4f142a',
blockNumber: 4,
from: '0x0445c33bdce670d57189158b88c0034b579f37ce',
to: '0x3797c825cac4a1fa765f6d8cd7787fb195849555',
gasUsed: 30548,
...
Transactions which attempt to transfer (such as mint
) will revert whilst paused.
truffle(develop)> await token.mint('0x46b68a577f95d02d2732cbe93c1809e9ca25b443','1000000000000000000000')
Thrown:
Error: Returned error: VM Exception while processing transaction: revert ERC20Pausable: token transfer while paused -- Reason given: ERC20Pausable: token transfer while paused.
...
Video
I made an asciinema going through all the steps in the tutorial:
https://asciinema.org/a/364291
Next steps
- We could deploy using
truffle migrate
and the following migration script
// migrations/2_deploy.js
const Token = artifacts.require('@openzeppelin/contracts/ERC20PresetMinterPauser');
module.exports = function(deployer) {
deployer.deploy(Token, 'My Token', 'MYT');
};
- We could deploy to a public test network (see Connecting to Public Test Networks with Truffle)
- We could extend
ERC20PresetMinterPauser
- If we write contract code we should write automated tests
- Points to consider when creating a fungible token (ERC20, ERC777)