ERC20
An ERC20 token contract keeps track of fungible tokens: any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC20 tokens useful for things like a medium of exchange currency, voting rights, staking, and more.
Constructing an ERC20 Token Contract
Using OpenZeppelin Contracts, we can easily create our own ERC20 token contract, which will be used to track Gold (GLD), an internal currency in a hypothetical game.
Here’s what our GLD token might look like.
// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract GLDToken is ERC20 {
constructor(uint256 initialSupply) ERC20("Gold", "GLD") {
_mint(msg.sender, initialSupply);
}
}
OpenZeppelin contracts are often used via
inheritance,
and here we’re reusing ERC20
for both
the basic standard implementation and the
name
,
symbol
, and
decimals
optional
extensions. Additionally, we’re creating an initialSupply
of tokens,
which will be assigned to the address that deploys the contract.
That’s it! Once deployed, we will be able to query the deployer’s balance:
> GLDToken.balanceOf(deployerAddress)
1000000000000000000000
We can also transfer these tokens to other accounts:
> GLDToken.transfer(otherAddress, 300000000000000000000)
> GLDToken.balanceOf(otherAddress)
300000000000000000000
> GLDToken.balanceOf(deployerAddress)
700000000000000000000
A Note on decimals
Often, you’ll want to be able to divide your tokens into arbitrary
amounts: say, if you own 5 GLD
, you may want to send 1.5 GLD
to a
friend, and keep 3.5 GLD
to yourself. Unfortunately, Solidity and the
EVM do not support this behavior: only integer (whole) numbers can be
used, which poses an issue. You may send 1
or 2
tokens, but not
1.5
.
To work around this, ERC20
provides a
decimals
field, which is
used to specify how many decimal places a token has. To be able to
transfer 1.5 GLD
, decimals
must be at least 1
, since that number
has a single decimal place.
How can this be achieved? It’s actually very simple: a token contract
can use larger integer values, so that a balance of 50
will represent
5 GLD
, a transfer of 15
will correspond to 1.5 GLD
being sent, and
so on.
It is important to understand that decimals
is only used for display
purposes. All arithmetic inside the contract is still performed on
integers, and it is the different user interfaces (wallets, exchanges,
etc.) that must adjust the displayed values according to decimals
. The
total token supply and balance of each account are not specified in
GLD
: you need to divide by 10 ** decimals
to get the actual GLD
amount.
You’ll probably want to use a decimals
value of 18
, just like Ether
and most ERC20 token contracts in use, unless you have a very special
reason not to. When minting tokens or transferring them around, you will
be actually sending the number num GLD * (10 ** decimals)
.
By default, ERC20
uses a value of 18
for decimals
. To use a
different value, you will need to override the decimals()
function in
your contract.
function decimals() public view virtual override returns
(uint8) { return 16; }
So if you want to send 5
tokens using a token contract with 18
decimals, the method to call will actually be:
transfer(recipient, 5 * (10 ** 18));