Discovering which token standards are supported by a contract
· Jim McDonald · 4 minutes read · 798 words ·There are now an increasing number of standards that any given token contract might support. This article explains how to query a token contract to identify which of the main standards ERC-20⧉ , ERC-721⧉ , and ERC-777⧉ are supported.
Some standards clash with other standards. Specifically:
- It is not possible for a single token contract to support both ERC-20 and ERC-721
- It is not possible for a single token contract to support both ERC-721 and ERC-777
Note that it is possible for a single token contract to support both ERC-20 and ERC-777.
ERC-777
The ERC-777 standard states that compliant contracts must register themselves with the ERC-1820⧉ registry. As such, it can be definitively asserted that a contract supports ERC-777 (or not) by carrying out the appropriate query of the registry. For example:
contract ERC1820Registry {
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address);
}
contract ERC777Checker {
ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
function supportsERC777(address _contract) public view returns (bool) {
bytes32 interfaceHash = keccak256(abi.encodePacked("ERC777Token"));
return ERC1820REGISTRY.getInterfaceImplementer(_contract, interfaceHash) != address(0);
}
}
If this function returns true
, the contract definitely supports ERC-777. Conversely, if this function returns false
, ERC-777 is definitely not supported.
ERC-721
The ERC-721 standard states that compliant contracts must register themselves with the ERC-165⧉ registry. As such, it can be definitely asserted that a contract supports ERC-721 by carrying out the appropriate query of the registry. For example:
contract ERC1820Registry {
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address);
}
contract ERC721Checker {
ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
function supportsERC721(address _contract) public view returns (bool) {
bytes32 interfaceID = 0x80ac58cd00000000000000000000000000000000000000000000000000000000;
return ERC1820REGISTRY.getInterfaceImplementer(_contract, interfaceID) != address(0);
}
}
Note that supportsERC721()
queries the ERC-1820 registry. This is because ERC-1820 will pass ERC-165 queries on to the ERC-165 registry, meaning that users can query the same registry to find information about support of both ERC-777 and ERC-165.
If this function returns true
, the contract definitely supports ERC-721. Conversely, if this function returns false
, ERC-721 is definitely not supported.
ERC-20
ERC-20 became a standard before the idea of registries were in place. As such, identifying whether a contract supports ERC-20 or not is more complex.
As mentioned above, it is possible for a contract to be both ERC-777 and ERC-20 compatible. Such contracts are required by the ERC-777 standard to explicitly register themselves with the ERC-1820 registry, so a first step would be to check there. For example:
contract ERC1820Registry {
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address);
}
contract ERC20Checker {
ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
function supportsERC20(address _contract) public view returns (bool) {
bytes32 interfaceHash = keccak256(abi.encodePacked("ERC20Token"));
return ERC1820REGISTRY.getInterfaceImplementer(_contract, interfaceHash) != address(0);
}
}
If supportsERC20()
returns true
the contract definitely supports ERC-20. However, if it returns false
, this simply means that the contract does not support both ERC-20 and ERC-777. It is still possible that the contract may support ERC-20 so further tests should be undertaken.
ERC-20 defines a number of functions, but the only mandatory view function that does not require arguments is totalSupply()
, which returns the total number of tokens that are in circulation. Hence the next test is to attempt to call this function, for example:
contract ERC20 {
function totalSupply() external view returns (uint256);
}
contract ERC20Checker {
function probablySupportsERC20(address _contract) public view returns (bool) {
return ERC20(_contract).totalSupply() != 0;
}
}
If probablySupportsERC20()
returns true
this is probably an ERC-20 token contract (it cannot be guaranteed as it is possible that the contract is not ERC-20 but does have a totalSupply()
function for its own purposes). If it returns false
this is probably not an ERC-20 token contract (the only situation in which this will return 0
for a valid ERC-20 tokens is if the circulating supply of tokens is 0
, in which case the token contract is not very interesting).
Further tests can be undertaken if a higher degree of certainty is required. If an address is known to contain a token balance a call to the balanceOf()
funcion can be undertaken to attempt to obtain the expected result. Similarly, querying other ERC-20 attributes such as the token name, symbol, etc. can help (but be aware these functions are optional so might not return anything).
Putting it together
The flowchart below provides the steps to follow in order to provide the best understanding as to which token standards are supported by any given contract:
A final word of warning: even if a token contract states that it supports a given standard there is still the possibility that the implementation has bugs or other problems that result in unexpected behaviour. Transactions against a contract should always be verified by comparing state before and after (e.g. ensuring that a transfer updates sender and recipient balances as expected).