Ethereum Tokens
Contents
Tokens are fungible when we can substitute any single unit of the token for another without any difference in its value or function.
- Strictly speaking, if a token’s historical provenance can be tracked, then it is not entirely fungible. The ability to track provenance can lead to blacklisting and whitelisting, reducing or eliminating fungibility.
The majority of projects are using tokens in one of two ways: either as “utility tokens” or as “equity tokens.” Very often, those two roles are conflated.
- Utility tokens are those where the use of the token is required to gain access to a service, application, or resource.
- Equity tokens are those that represent shares in the control or ownership of something, such as a startup.
On Ethereum tokens are different from ether because the Ethereum protocol does not know anything about them.
- Sending ether is an intrinsic action of the Ethereum platform, but sending or even owning tokens is not.
- The ether balance of Ethereum accounts is handled at the protocol level, whereas the token balance of Ethereum accounts is handled at the smart contract level.
- In order to create a new token on Ethereum, you must create a new smart contract. Once deployed, the smart contract handles everything, including ownership, transfers, and access rights.
Token Standards
ERC20
ERC20 was introduced in November 2015 as an Ethereum Request for Comments (ERC).
- It was automatically assigned GitHub issue number 20, giving rise to the name “ERC20 token.”
- It became Ethereum Improvement Proposal 20 (EIP-20).
ERC20 is a standard for fungible tokens, meaning that different units of an ERC20 token are interchangeable and have no unique properties.
- The ERC20 token standard only tracks the final balance of each account and does not (explicitly) track the provenance of any token.
The ERC20 standard defines a common interface for contracts implementing a token, such that any compatible token can be accessed and used in the same way.
ERC20 required functions and events:
totalSupply
: Returns the total units of this token that currently exist.- ERC20 tokens can have a fixed or a variable supply.
balanceOf
: Given an address, returns the token balance of that address.transfer()
: Given an address and amount, transfers that amount of tokens to that address, from the balance of the address that executed the transfer.- This workflow is the one used by wallets to send tokens to other wallets.
transferFrom()
: Given a sender, recipient, and amount, transfers tokens from one account to another.- Used in combination with
approve
, a two-transaction workflow that usesapprove
followed bytransferFrom
.- This workflow allows a token owner to delegate their control to another address. It is most often used to delegate control to a contract for distribution of tokens, but it can also be used by exchanges.
- Used in combination with
approve()
: Given a recipient address and amount, authorizes that address to execute several transfers up to that amount, from the account that issued the approval.allowance
: Given an owner address and a spender address, returns the remaining amount that the spender is approved to withdraw from the owner.Transfer
: Event triggered upon a successful transfer (call totransfer
ortransferFrom
) (even for zero-value transfers).Approval
: Event logged upon a successful call toapprove
.
ERC20 optional functions:
name
: Returns the human-readable name of the token.symbol
: Returns a human-readablesymbol
for the token.decimals
: Returns the number of decimals used to divide token amounts.- If decimals is 2, then the token amount is divided by 100 to get its user representation.
The ERC20 interface defined in Solidity:
contract ERC20 {
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
}
ERC20 data structures:
mapping(address => uint256) balances;
mapping (address => mapping (address => uint256)) public allowed;
: The primary key is the address of the token owner, mapping to a spender address and an allowance amount.
Two implementations:
If a user incorrectly attempts to transfer ERC20 tokens to a contract address and that contract is not equipped to receive ERC20 tokens, the tokens will be lost.
- Many exchanges publish receiving addresses that are actually contracts. These contracts are only meant to receive ether, not ERC20 tokens, most often sweeping all funds sent to them to “cold storage” or another centralized wallet.
Subtle differences between tokens and ether:
- Ether is transferred by a transaction that has a recipient address as its destination. Token transfers occur within the specific token contract state and have the token contract as their destination, not the recipient’s address.
- In a token transfer, no transaction is actually sent to the recipient of the token. Instead, the recipient’s address is added to a map within the token contract itself. The token contract tracks balances and issues events.
- A transaction sending ether to an address changes the state of an address. A transaction transferring a token to an address only changes the state of the token contract, not the state of the recipient address.
- Even a wallet that has support for ERC20 tokens is not aware of a token balance unless the user explicitly adds a specific token contract to “watch.”
- Ether is sent with the
send
function and accepted by any payable function in a contract or any externally owned address. Tokens are sent usingtransfer
orapprove
&transferFrom
functions that exist only in the ERC20 contract, and do not (at least in ERC20) trigger any payable functions in a recipient contract. - To send ether or use any Ethereum contract you need ether to pay for gas. To send tokens, you also need ether. You cannot pay for a transaction’s gas with a token, and the token contract can’t pay for the gas for you either.
ERC721
The ERC721 proposal is for a standard for non-fungible tokens, also known as deeds.
- Non-fungible tokens track ownership of a unique thing.
- The use of the word “deed” is intended to reflect the “ownership of property” part.
-
deed: A legal document that is signed and delivered, especially one regarding the ownership of property or legal rights.
-
- The ERC721 standard places no limitation or expectation on the nature of the thing whose ownership is tracked by a deed and requires only that it can be uniquely identified, which in the case of this standard is achieved by a 256-bit identifier.
- Continued discussion
// Mapping from deed ID to owner
mapping (uint256 => address) private deedOwner;
ERC721 tracks each deed ID and who owns it, with the deed ID being the primary key of the mapping.
- ERC20 tracks the balances that belong to each owner, with the owner being the primary key of the mapping.
The ERC721 contract interface specification:
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
interface ERC721 /* is ERC165 */ {
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
interface ERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
// optional
interface ERC721Metadata /* is ERC721 */ {
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string);
}
// optional
interface ERC721Enumerable /* is ERC721 */ {
function totalSupply() external view returns (uint256);
function tokenByIndex(uint256 _index) external view returns (uint256);
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}
ERC223
The ERC223 proposal attempts to solve the problem of inadvertent transfer of tokens to a contract (that may or may not support tokens) by detecting whether the destination address is a contract or not.
- ERC223 requires that contracts designed to accept tokens implement a function named
tokenFallback
. - If the destination of a transfer is a contract and the contract does not have support for tokens (i.e., does not implement
tokenFallback
), the transfer fails.
ERC223 is not widely implemented, and there is some debate about backward compatibility and trade-offs between implementing changes at the contract interface level versus the user interface.
ERC777
ERC777 is another proposal for an improved token contract standard
ERC777 depends on a parallel proposal for a registry contract, specified in ERC820.
- Some of the debate on ERC777 is about the complexity of adopting two big changes at once: a new token standard and a registry standard.
Using Token Standards
Token standards are the minimum specifications for an implementation.
The primary purpose of these standards is to encourage interoperability between contracts.
References