Lesson: Ethereum tokens

A token is a representation of something on the blockchain. This can represent money, time, services, shares in a company, a virtual pet, anything. By representing things as tokens, we can allow smart contracts to interact with them, exchange them, create or destroy them.

A token contract is simply an Ethereum smart contract. "Sending tokens" actually means "calling a method on a smart contract that someone has written and deployed". At the end of the day, a token contract is not much more than a mapping of addresses to balances, plus some methods to add and subtract from those balances.

It is these balances that represent the tokens themselves. Someone "has tokens" when their balance in the token contract is non-zero. That's it! These balances could be considered money, experience points in a game, deeds of ownership, or voting rights, and each of these tokens would be stored in different token contracts.

Fungible tokens

Fungible tokens are tokens where any one of them is exactly equal to any other; no token has special rights or behavior associated with it. Stated differently, fungible tokens are digital assets that are interchangeable and possess equal value, making them an important component of the cryptocurrency ecosystem. Fungible tokens are useful for things like a medium of exchange currency, voting rights, staking, and more.

One example of fungible tokens is stablecoins such as Tether (USDT) and USD Coin (USDC), which offer a stable medium of exchange by being pegged to stable assets like the US dollar, mitigating the volatility commonly associated with other cryptocurrencies.

ERC-20

Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of standards for documenting how a contract can interoperate with other contracts. These are technical documents that outline standards for programming on Ethereum, called Ethereum Request for Comment (ERC).

The Ethereum standard for fungible tokens is ERC-20. Technically, ERC-20 is a list of functions and events that must be implemented by a token, so that it can be considered ERC-20 compliant. Because ERC-20 tokens follow a standardized set of rules, they can be easily exchanged and used within various applications. Keep in mind that, unlike ETH (Ethereum's native cryptocurrency), ERC-20 tokens aren't directly held by accounts. The tokens only exist inside a contract, which is like a self-contained database.

An ERC-20 token must implement the following functions:

totalSupply()Returns the total token supply.
balanceOf(addr)Returns the token balance of the account with address addr.
transfer(to, value)Transfers value amount of tokens to address to.
approve(spender, value)Allows spender to withdraw from your account multiple times, up to the value amount.
allowance(owner, spender)Returns the amount which spender is still allowed to withdraw from owner.
transferFrom(from, to, value)Transfers value amount of tokens from address from to address to.

Additionally, we also have: decimals, name, and symbol; which are optional, but almost always included.

Notice how it's possible to transfer tokens that belong to someone else (of course, with a previous approval). In other words, it's possible to authorize someone – or another contract – to transfer funds on our behalf. A possible use case involves paying for subscription-based services, where we don't want to manually send a payment every day/week/month. Instead, we just let a program do it for us.

To ensure transparency and allow external applications (like wallets and explorers) to track state changes, ERC-20 contracts must emit two key events: Transfer (which logs the movement of tokens), and Approval (which logs changes in spending permissions).

Decimals

Recall how we discussed both Ether and Wei in our previous lectures and labs (and we only communicated with the blockchain in Wei). Often, we'll want to be able to divide our tokens into arbitrary amounts: say, if we own 5 USDX (a fictional token), we may want to send 1.5 USDX to a friend, and keep 3.5 USDX to ourselves. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. We may send 1 or 2 tokens, but not 1.5.

To work around this, ERC-20 provides a decimals field, which is used to specify how many decimal places a token has. To be able to transfer 1.5 USDX, 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 USDX, a transfer of 15 will correspond to 1.5 USDX being sent, and so on.

It is important to understand that the decimals value 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.

Most of the time, we want to use a decimals value of 18, just like Ether and most ERC-20 token contracts in use, unless we have a very special reason not to.

Different kinds of tokens

Note that there's a big difference between having two voting rights and two deeds of ownership: each vote is equal to all others, but houses usually are not! We already mentioned that this attribute is called fungibility. Fungible goods are equivalent and interchangeable, like Ether, fiat currencies, and voting rights. Non-fungible goods are unique and distinct, like deeds of ownership, or collectibles. In a nutshell, when dealing with non-fungibles (like our house) we care about which ones we have, while in fungible assets (like our bank account statement) what matters is how much we have.

The two most well-known Ethereum token standards are listed below, but note that there are other standards as well:

ERC-20The most widespread token standard for fungible assets.
ERC-721The de-facto solution for non-fungible tokens, often used for collectibles, art, and games.

Understanding the difference between fungible tokens and NFTs is essential for navigating the crypto landscape effectively.

OpenZeppelin

OpenZeppelin Contracts is a library for secure smart contract development on the Ethereum network, written in Solidity. It's built on a solid foundation of community-vetted code, and features implementations of standards like ERC-20 and ERC-721. We can use the contracts in the OpenZeppelin library by importing them, and using inheritance (which is something we learned when we studied Solidity).

Therefore, using smart contracts, we can easily create our own ERC-20 token contract (in the example below, a token called Pax Gold with a symbol PAXG). Additionally, we're creating/minting an initial supply of tokens, which will be assigned to the address that deploys the contract.

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

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

contract PaxGold is ERC20 {
    constructor(uint256 initialSupply) ERC20("Pax Gold", "PAXG") {
        // Mint initial supply to the deployer
        _mint(msg.sender, initialSupply);
    }
}

That's it! We can now deploy a new ERC-20 fungible token to the Ethereum blockchain.