ERC-1155 Multi-token standard (Multi Token Standard)
Final · NFTs & multi-token · created 2018-06-17
A standard that lets a single contract manage several kinds of tokens, such as fungible tokens and NFTs, together and send several tokens in one transaction.
Why it is needed
Existing token standards such as ERC-20 and ERC-721 require one contract to be deployed for each token type or collection. The same code is uploaded to the blockchain over and over, and each token has its own address and permissions, which makes tokens hard to manage together. When a blockchain game has thousands of item types, this approach cannot keep up. Moving several tokens at once requires sending a separate transaction for each type, which raises fees, and using an exchange or swap contract requires granting approval to each token contract individually. ERC-1155 solves this by defining a different type of token for each token ID within a single contract and by allowing several tokens to be sent in one call. It is not limited to games, and other apps that need this flexibility can use it too.
What to implement
event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value);: Emitted when one type of token is moved. For a mint, _from is the zero address, and for a burn, _to is the zero address.event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);: Emitted when several types of tokens are moved at once. _ids and _values must match in order and length.event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);: Emitted when an operator is granted or stripped of permission to manage all of an owner's tokens.event URI(string _value, uint256 indexed _id);: Emitted when the metadata URI of a token ID changes. The URI must point to a metadata JSON file.function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;: Sends _value units of one type of token. If the recipient is a contract, it calls onERC1155Received to confirm acceptance.function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;: Sends several types of tokens at once. If the recipient is a contract, it calls the receiver hook to confirm acceptance.function balanceOf(address _owner, uint256 _id) external view returns (uint256);: Returns the balance of a specific token ID held by an account.function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);: Returns the balances for several (account, token ID) pairs at once.function setApprovalForAll(address _operator, bool _approved) external;: Allows or revokes an operator's permission to manage all of the caller's tokens. Emits an ApprovalForAll event on success.function isApprovedForAll(address _owner, address _operator) external view returns (bool);: Returns whether an operator is authorized to manage the owner's tokens.function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4);: Called when the receiving contract receives one type of token. Returns 0xf23a6e61 to accept and reverts to reject.function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4);: Called when the receiving contract receives several types of tokens. Returns 0xbc197c81 to accept and reverts to reject.function uri(uint256 _id) external view returns (string memory);: Returns the metadata URI of a token ID. It belongs to the optional extension ERC1155Metadata_URI.
In depth
Full interface implementation and the ERC-165 identifier
A contract that implements ERC-1155 must implement all functions of the ERC1155 interface. It must also implement supportsInterface from ERC-165 and return true when the interfaceID is 0xd9b67a26. The _id argument of every function refers to the token or token type handled in the transaction. Instead of splitting contracts by type as in ERC-20 or giving a whole collection a single configuration as in ERC-721, each token ID can have a token type with different attributes such as metadata and supply. The identifier of the ERC1155TokenReceiver interface used by contracts that receive tokens is 0x4e2312e0, and the identifier of the optional extension ERC1155Metadata_URI is 0x0e89341c.
Rules for safeTransferFrom and safeBatchTransferFrom
Both functions require the caller to be authorized to manage the tokens of the _from account, and they must revert if _to is the zero address. They also revert if the holder's balance is less than the _value being sent, and on any other error. A single transfer must emit a TransferSingle event. A batch transfer must revert if _ids and _values differ in length, and must record every balance change with TransferSingle or TransferBatch events. In a batch transfer, balance changes and events must follow array order (_ids[0]/_values[0] before _ids[1]/_values[1]). Once all conditions are met, the function must check whether _to is a contract (code size > 0), and if so call onERC1155Received or onERC1155BatchReceived and act on the result. The _data passed by the sender must be forwarded to the hook unchanged. Because the standard supports only safe transfers, a receiving contract can trust that the hook is always called at the end of a transfer.
Event rules, minting and burning
TransferSingle or TransferBatch must be emitted whenever tokens move, including zero-amount transfers, mints and burns. _operator is the address authorized to make the transfer (msg.sender is preferred), _from is the holder whose balance decreases, and _to is the recipient whose balance increases. A mint (creation) is treated as a special transfer with _from set to 0x0, and a burn (destruction) as one with _to set to 0x0. A burn does not need to actually send anything to 0x0; it is enough that _to in the event is 0x0. Subtracting the total sent to 0x0 from the total that came from 0x0 lets clients and exchanges compute the circulating supply per token ID. To announce only that a token ID exists, without any balance, it is preferred to emit a TransferSingle from 0x0 to 0x0 with the creator as _operator and a _value of 0. Initial balances assigned at contract creation must also emit events, and the contract state must always be traceable from events alone. All events must be emitted before the receiver hook is called, and the state balances and event balances must match before any external call, so the order stays correct even if the receiving contract reenters by sending the tokens again.
ERC1155TokenReceiver rules a receiving contract must follow
A contract that wants to receive tokens must implement both onERC1155Received and onERC1155BatchReceived and return true for supportsInterface(0x4e2312e0). This supportsInterface must also return true for 0x01ffc9a7 and must not use more than 10,000 gas. The hook is called at the very end of the transfer, after balances are updated and events are emitted. If it accepts the transfer, onERC1155Received returns 0xf23a6e61 and onERC1155BatchReceived returns 0xbc197c81, and to reject it reverts. If it returns any other value or reverts, the caller must revert the transaction. If the recipient is a regular account (EOA), the hook is not called, and it is not called outside of a mint or transfer either. A single transaction can call the hook several times. Each call represents a non-overlapping balance change, and together the calls must account for every balance change in submission order. If a hook passes tokens on to another address, this is treated as accepting and then starting a new transfer, and it must return the magic value if that succeeds. A contract sending to itself may skip the hook.
Non-standard transfer functions and ERC-721 hybrids
Even if an implementation-specific transfer function other than safeTransferFrom is used (for example myTransferFrom), the balance update and event rules must be followed in the same way, and if the recipient is a contract, the hook must be called and its return value respected. However, while the standard functions must revert if the receiving contract does not implement ERC1155TokenReceiver, a non-standard function should preferably revert but may proceed. To proceed, it calls supportsInterface(0x4e2312e0). If true is returned, the recipient is a valid receiver that failed to accept, so it must revert. If the call fails or another value is returned, it may be treated as success. In this case, tokens may be sent to an address that is not ready to receive them, and they may not be recoverable. A hybrid implementation mixed with ERC-721 or similar must give the receiving contract at least 10,000 gas and call supportsInterface(0x4e2312e0) first, before calling the hook. If it returns true, it proceeds like regular ERC-1155, and otherwise it may follow the transfer rules of the other standard. Either way, the ERC-1155 transfer event must be emitted. The source recommends a pure single-standard implementation over a hybrid.
Metadata URI and {id} substitution
If the optional extension ERC1155Metadata_URI is included, supportsInterface(0x0e89341c) returns true and the uri(_id) function is provided. The URI is in RFC 3986 format and must point to a JSON file that follows the ERC-1155 metadata JSON schema. If the URI contains the string {id}, clients must replace it with the hexadecimal of the actual token ID. This uses only lowercase [0-9a-f] with no 0x prefix, left-padded with zeros to 64 characters. For example, for token ID 314592 (0x4CCE0), {id} in https://token-cdn-domain/{id}.json is replaced with 000000000000000000000000000000000000000000000000000000000004cce0. An {id} inside JSON values is replaced by the same rule. When the URI changes, the URI event must be emitted as far as the change can be expressed as an event, and emitting it at mint time is optional. The uri function must return the same value as the last event, and because it may return a string even when the token does not exist, it must not be used to check whether a token exists. The JSON has the fields name, decimals, description, image and properties, and if a localization object (uri, default, locales) is included, {locale} can be replaced to fetch per-language data.
Approval model and design rationale
setApprovalForAll lets an operator manage all of the approver's tokens on their behalf, and it must emit the ApprovalForAll event on success. The approval status is queried with isApprovedForAll. To approve only some token IDs, it proposes using a separate interface such as the ERC-1761 scoped approval interface. The owner must always be treated as able to handle their own tokens regardless of approval, so there is no need to approve oneself as an operator. As design rationale, the symbol function was left out because it is not broadly useful for identifying generic virtual assets and collides often, and the name function was left out because the metadata JSON is treated as the source of truth for names, which reduces onchain duplication and per-language storage costs. A separate single transfer function exists because single-token transfers are common and much cheaper. In an upgrade that redeploys to a new address, the new contract must emit events that reproduce the previous state, and a minimal set of events reflecting only final balances is enough. To avoid this, it recommends considering a proxy pattern such as EIP-2535.
How to mix NFTs and fungible tokens, and enumeration
The standard does not mandate how to represent NFTs and only gives examples. The first is ID bit splitting, which uses the upper 128 bits of a uint256 _id as the base token ID and the lower 128 bits as the index of an individual NFT. The base ID combined with 0 refers to the whole type, and adding the index refers to an individual token, where the index should preferably start at 1. The value 128 is arbitrary, so implementations may split differently, and an observer who wants to know the type from the ID alone must know that implementation's splitting scheme. The second is the natural approach of setting the maximum supply of each NFT to 1. Enumeration (finding which IDs and quantities exist) should be done through event logs to save storage, and exchanges and explorers are advised to read the TransferSingle, TransferBatch and URI events from the deployment block and keep a local DB holding token IDs, supplies and URIs. Sorting IDs and quantities in ascending order when sending a batch transfer can save a large amount of gas in some implementations. The status of this standard is Final.