ERC-998: Composable Non-Fungible Token
Extends a ERC-721 to own other ERC-721 and ERC-20 tokens.
Abstract
An extension of the ERC-721 standard to enable ERC-721 tokens to own other ERC-721 tokens and ERC-20 tokens.
An extension of the ERC-20 and ERC-223 https://github.com/ethereum/EIPs/issues/223
standards to enable ERC-20 and ERC-223
tokens to be owned by ERC-721 tokens.
This specification covers four different kinds of composable tokens:
ERC998ERC721
top-down composable tokens that receive, hold and transfer ERC-721 tokensERC998ERC20
top-down composable tokens that receive, hold and transfer ERC-20 tokensERC998ERC721
bottom-up composable tokens that attach themselves to other ERC-721 tokens.ERC998ERC20
bottom-up composable tokens that attach themselves to ERC-721 tokens.
which map to
- An
ERC998ERC721
top-down composable is an ERC-721 token with additional functionality for owning other ERC-721 tokens. - An
ERC998ERC20
top-down composable is an ERC-721 token with additional functionality for owning ERC-20 tokens. - An
ERC998ERC721
bottom-up composable is an ERC-721 token with additional functionality for being owned by an ERC-721 token. - An
ERC998ERC20
bottom-up composable is an ERC-20 token with additional functionality for being owned by an ERC-721 token.
A top-down composable contract stores and keeps track of child tokens for each of its tokens.
A bottom-up composable contract stores and keeps track of a parent token for each its tokens.
With composable tokens it is possible to compose lists or trees of ERC-721 and ERC-20 tokens connected by ownership. Any such structure will have a single owner address at the root of the structure that is the owner of the entire composition. The entire composition can be transferred with one transaction by changing the root owner.
Different composables, top-down and bottom-up, have their advantages and disadvantages which are explained in the Rational section. It is possible for a token to be one or more kinds of composable token.
A non-fungible token is compliant and Composable of this EIP if it implements one or more of the following interfaces:
ERC998ERC721TopDown
ERC998ERC20TopDown
ERC998ERC721BottomUp
ERC998ERC20BottomUp
Specification
ERC-721
ERC998ERC721
top-down, ERC998ERC20
top-down, and ERC998ERC721
bottom-up composable contracts must implement the ERC-721 interface.
ERC-20
ERC998ERC20
bottom-up composable contracts must implement the ERC-20 interface.
ERC-165
The ERC-165 standard must be applied to each ERC-998 interface that is used.
Authentication
Authenticating whether a user or contract can execute some action works the same for both ERC998ERC721
top-down and ERC998ERC721
bottom-up composables.
A rootOwner
refers to the owner address at the top of a tree of composables and ERC-721 tokens.
Authentication within any composable is done by finding the rootOwner and comparing it to msg.sender
, the return result of getApproved(tokenId)
and the return result of isApprovedForAll(rootOwner, msg.sender)
. If a match is found then authentication passes, otherwise authentication fails and the contract throws.
Here is an example of authentication code:
The approve(address _approved, uint256 _tokenId)
and getApproved(uint256 _tokenId)
ERC-721 functions are implemented specifically for the rootOwner. This enables a tree of composables to be transferred to a new rootOwner without worrying about which addresses have been approved in child composables, because any prior approves can only be used by the prior rootOwner.
Here are example implementations:
Traversal
The rootOwner of a composable is gotten by calling rootOwnerOf(uint256 _tokenId)
or rootOwnerOfChild(address _childContract, uint256 _childTokenId)
. These functions are used by top-down and bottom-up composables to traverse up the tree of composables and ERC-721 tokens to find the rootOwner.
ERC998ERC721
top-down and bottom-up composables are interoperable with each other. It is possible for a top-down composable to own a bottom-up composable or for a top-down composable to own an ERC-721 token that owns a bottom-up token. In any configuration calling rootOwnerOf(uint256 _tokenID)
on a composable will return the root owner address at the top of the ownership tree.
It is important to get the traversal logic of rootOwnerOf
right. The logic for rootOwnerOf
is the same whether or not a composable is bottom-up or top-down or both.
Here is the logic:
Calling rootOwnerOfChild
for a token means the following logic:
But understand that the real call to rootOwnerOfChild
should be made with assembly so that the code can check if the call failed and so that the staticcall
opcode is used to ensure that no state is modified.
Tokens/contracts that implement the above authentication and traversal functionality are "composable aware".
Composable Transfer Function Parameter Format
Composable functions that make transfers follow the same parameter format: from:to:what.
For example the getChild(address _from, uint256 _tokenId, address _childContract, uint256 _childTokenId)
composable function transfers an ERC-721 token from an address to a top-down composable. The _from
parameter is the from, the _tokenId
parameter is the to and the address _childContract, uint256 _childTokenId
parameters are the what.
Another example is the safeTransferChild(uint256 _fromTokenId, address _to, address _childContract, uint256 _childTokenId)
function. The _fromTokenId
is the from, the _to
is the to and the address _childContract, address _childTokenId
parameters are the what.
transferFrom/safeTransferFrom Functions Do Not Transfer Tokens Owned By Tokens
In bottom-up and top-down composable contracts the transferFrom
and safeTransferFrom
functions must throw if they are called directly to transfer a token that is owned by another token.
The reason for this is that these functions do not explicitly specify which token owns a token to be transferred. See the rational section for more information about this.
transferFrom/safeTransferFrom
functions must be used to transfer tokens that are owned by an address.
ERC-721 Top-Down Composable
ERC-721 top-down composables act as containers for ERC-721 tokens.
ERC-721 top-down composables are ERC-721 tokens that can receive, hold and transfer ERC-721 tokens.
There are two ways to transfer a ERC-721 token to a top-down composable:
- Use the
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data)
function. The_to
argument is the top-down composable contract address. Thebytes data
argument holds the integer value of the top-down composable tokenId that the ERC-721 token is transferred to. - Call
approve
in the ERC-721 token contract for the top-down composable contract. Then callgetChild
in the composable contract.
The first ways is for ERC-721 contracts that have a safeTransferFrom
function. The second way is for contracts that do not have this function such as cryptokitties.
Here is an example of transferring ERC-721 token 3 from an address to top-down composable token 6:
Every ERC-721 top-down composable compliant contract must implement the ERC998ERC721TopDown
interface.
The ERC998ERC721TopDownEnumerable
and ERC998ERC20TopDownEnumerable
interfaces are optional.
rootOwnerOf
1
This function traverses token owners until the root owner address of _tokenId
is found.
The first 4 bytes of rootOwner contain the ERC-998 magic value 0xcd740db5
. The last 20 bytes contain the root owner address.
The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a rootOwnerOf
function. The magic value is used in such calls to ensure a valid return value is received.
If it is unknown whether a contract has the rootOwnerOf
function then the first four bytes of the rootOwner
return value must be compared to 0xcd740db5
.
0xcd740db5
is equal to:
Here is an example of a value returned by rootOwnerOf
.
0xcd740db50000000000000000e5240103e1ff986a2c8ae6b6728ffe0d9a395c59
rootOwnerOfChild
This function traverses token owners until the root owner address of the supplied child token is found.
The first 4 bytes of rootOwner contain the ERC-998 magic value 0xcd740db5
. The last 20 bytes contain the root owner address.
The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a rootOwnerOf
function. The magic value is used in such calls to ensure a valid return value is received.
If it is unknown whether a contract has the rootOwnerOfChild
function then the first four bytes of the rootOwner
return value must be compared to 0xcd740db5
.
ownerOfChild
This function is used to get the parent tokenId of a child token and get the owner address of the parent token.
The first 4 bytes of parentTokenOwner contain the ERC-998 magic value 0xcd740db5
. The last 20 bytes contain the parent token owner address.
The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a ownerOfChild
function. The magic value is used in such calls to ensure a valid return value is received.
If it is unknown whether a contract has the ownerOfChild
function then the first four bytes of the parentTokenOwner
return value must be compared to 0xcd740db5
.
onERC721Received
This is a function defined in the ERC-721 standard. This function is called in an ERC-721 contract when safeTransferFrom
is called. The bytes _data
argument contains an integer value from 1 to 32 bytes long that is the parent tokenId that an ERC-721 token is transferred to.
The onERC721Received
function is how a top-down composable contract is notified that an ERC-721 token has been transferred to it and what tokenId in the top-down composable is the parent tokenId.
The return value for onERC721Received
is the magic value 0x150b7a02
which is equal to bytes4(keccak256(abi.encodePacked("onERC721Received(address,address,uint256,bytes)")))
.
transferChild
This function authenticates msg.sender
and transfers a child token from a top-down composable to a different address.
This function makes this call within it:
safeTransferChild 1
This function authenticates msg.sender
and transfers a child token from a top-down composable to a different address.
This function makes this call within it:
safeTransferChild 2
This function authenticates msg.sender
and transfers a child token from a top-down composable to a different address or to a different top-down composable.
A child token is transferred to a different top-down composable if the _to
address is a top-down composable contract and bytes _data
is supplied an integer representing the parent tokenId.
This function makes this call within it:
transferChildToParent
This function authenticates msg.sender
and transfers a child bottom-up composable token from a top-down composable to a different ERC-721 token. This function can only be used when the child token is a bottom-up composable token. It is designed to transfer a bottom-up composable token from a top-down composable to an ERC-721 token (bottom-up style) in one transaction.
This function makes this call within it:
getChild
This function is used to transfer an ERC-721 token when its contract does not have a safeTransferChild(uint256 _fromTokenId, address _to, address _childContract, uint256 _childTokenId, bytes _data)
function.
A transfer with this function is done in two steps:
- The owner of the ERC-721 token calls
approve
orsetApprovalForAll
in the ERC-721 contract for the top-down composable contract. - The owner of the ERC-721 token calls
getChild
in the top-down composable contract for the ERC-721 token.
The getChild
function must authenticate that msg.sender
is the owner of the ERC-721 token in the ERC-721 contract or is approved or an operator of the ERC-721 token in the ERC-721 contract.
ERC-721 Top-Down Composable Enumeration
Optional interface for top-down composable enumeration:
ERC-20 Top-Down Composable
ERC-20 top-down composables act as containers for ERC-20 tokens.
ERC-20 top-down composables are ERC-721 tokens that can receive, hold and transfer ERC-20 tokens.
There are two ways to transfer ERC-20 tokens to an ERC-20 Top-Down Composable:
- Use the
transfer(address _to, uint256 _value, bytes _data);
function from theERC-223
contract. The_to
argument is the ERC-20 top-down composable contract address. The_value
argument is how many ERC-20 tokens to transfer. Thebytes
argument holds the integer value of the top-down composable tokenId that receives the ERC-20 tokens. - Call
approve
in the ERC-20 contract for the ERC-20 top-down composable contract. Then callgetERC20(address _from, uint256 _tokenId, address _erc20Contract, uint256 _value)
from the ERC-20 top-down composable contract.
The first way is for ERC-20 contracts that support the ERC-223
standard. The second way is for contracts that do not.
ERC-20 top-down composables implement the following interface:
tokenFallback
This function comes from the ERC-223
which is an extension of the ERC-20 standard. This function is called on the receiving contract from the sending contract when ERC-20 tokens are transferred. This function is how the ERC-20 top-down composable contract gets notified that one of its tokens received ERC-20 tokens. Which token received ERC-20 tokens is specified in the _data
parameter.
balanceOfERC20
Gets the balance of ERC-20 tokens owned by a token from a specific ERC-20 contract.
transferERC20
This is used to transfer ERC-20 tokens from a token to an address. This function calls ERC20(_erc20Contract).transfer(_to, _value)
;
This function must authenticate msg.sender
.
transferERC223
This function is from the ERC-223
. It is used to transfer ERC-20 tokens from a token to an address or to another token by putting an integer token value in the _data
argument.
This function must authenticate msg.sender
.
getERC20
This function is used to transfer ERC-20 tokens to an ERC-20 top-down composable when an ERC-20 contract does not have a transferERC223(uint256 _tokenId, address _to, address _erc223Contract, uint256 _value, bytes _data)
function.
Before this function can be used the ERC-20 top-down composable contract address must be approved in the ERC-20 contract to transfer the ERC-20 tokens.
This function must authenticate that msg.sender
equals _from
or has been approved in the ERC-20 contract.
ERC-20 Top-Down Composable Enumeration
Optional interface for top-down composable enumeration:
ERC-721 Bottom-Up Composable
ERC-721 bottom-up composables are ERC-721 tokens that attach themselves to other ERC-721 tokens.
ERC-721 bottom-up composable contracts store the owning address of a token and the parent tokenId if any.
rootOwnerOf
This function traverses token owners until the root owner address of _tokenId
is found.
The first 4 bytes of rootOwner contain the ERC-998 magic value 0xcd740db5
. The last 20 bytes contain the root owner address.
The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a rootOwnerOf
function. The magic value is used in such calls to ensure a valid return value is received.
If it is unknown whether a contract has the rootOwnerOf
function then the first four bytes of the rootOwner
return value must be compared to 0xcd740db5
.
0xcd740db5
is equal to:
Here is an example of a value returned by rootOwnerOf
.
0xcd740db50000000000000000e5240103e1ff986a2c8ae6b6728ffe0d9a395c59
tokenOwnerOf
This function is used to get the owning address and parent tokenId of a token if there is one stored in the contract.
If isParent
is true then tokenOwner
is the owning ERC-721 contract address and parentTokenId
is a valid parent tokenId. If isParent
is false then tokenOwner
is a user address and parentTokenId
does not contain a valid parent tokenId and must be ignored.
The first 4 bytes of tokenOwner
contain the ERC-998 magic value 0xcd740db5
. The last 20 bytes contain the token owner address.
The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a tokenOwnerOf
function. The magic value is used in such calls to ensure a valid return value is received.
If it is unknown whether a contract has the rootOwnerOf
function then the first four bytes of the tokenOwner
return value must be compared to 0xcd740db5
.
transferToParent
This function is used to transfer a token from an address to a token. msg.sender
must be authenticated.
This function must check that _toToken
exists in _toContract
and throw if not.
transferFromParent
This function is used to transfer a token from a token to an address. msg.sender
must be authenticated.
This function must check that _fromContract
and _fromTokenId
own _tokenId
and throw not.
transferAsChild
This function is used to transfer a token from a token to another token. msg.sender
must be authenticated.
This function must check that _toToken
exists in _toContract
and throw if not.
This function must check that _fromContract
and _fromTokenId
own _tokenId
and throw if not.
ERC-721 Bottom-Up Composable Enumeration
Optional interface for bottom-up composable enumeration:
ERC-20 Bottom-Up Composable
ERC-20 bottom-up composables are ERC-20 tokens that attach themselves to ERC-721 tokens, or are owned by a user address like standard ERC-20 tokens.
When owned by an ERC-721 token, ERC-20 bottom-up composable contracts store the owning address of a token and the parent tokenId. ERC-20 bottom-up composables add several methods to the ERC-20 and ERC-223
interfaces allowing for querying the balance of parent tokens, and transferring tokens to, from, and between parent tokens.
This functionality can be implemented by adding one additional mapping to track balances of tokens, in addition to the standard mapping for tracking user address balances.
The complete interface is below.
balanceOfToken
This function returns the balance of a non-fungible token. It mirrors the standard ERC-20 method balanceOf
, but accepts the address of the parent token's contract, and the parent token's ID. This method behaves identically to balanceOf
, but checks for ownership by ERC-721 tokens rather than user addresses.
transferToParent
This function transfers an amount of tokens from a user address to an ERC-721 token. This function MUST ensure that the recipient contract implements ERC-721 using the ERC-165 supportsInterface
function. This function SHOULD ensure that the recipient token actually exists, by calling ownerOf
on the recipient token's contract, and ensuring it neither throws nor returns the zero address. This function MUST emit the TransferToParent
event upon a successful transfer (in addition to the standard ERC-20 Transfer
event!). This function MUST throw if the _from
account balance does not have enough tokens to spend.
transferFromParent
This function transfers an amount of tokens from an ERC-721 token to an address. This function MUST emit the TransferFromParent
event upon a successful transfer (in addition to the standard ERC-20 Transfer
event!). This function MUST throw if the balance of the sender ERC-721 token is less than the _amount
specified. This function MUST verify that the msg.sender
owns the sender ERC-721 token, and MUST throw otherwise.
transferFromParentERC223
This function transfers an amount of tokens from an ERC-721 token to an address. This function has identical requirements to transferFromParent
, except that it additionally MUST invoke tokenFallback
on the recipient address, if the address is a contract, as specified by ERC-223
.
transferAsChild 1
This function transfers an amount of tokens from an ERC-721 token to another ERC-721 token. This function MUST emit BOTH the TransferFromParent
and TransferToParent
events (in addition to the standard ERC-20 Transfer
event!). This function MUST throw if the balance of the sender ERC-721 token is less than the _amount
specified. This function MUST verify that the msg.sender
owns the sender ERC-721 token, and MUST throw otherwise. This function MUST ensure that the recipient contract implements ERC-721 using the ERC-165 supportsInterface
function. This function SHOULD ensure that the recipient token actually exists, by calling ownerOf
on the recipient token's contract, and ensuring it neither throws nor returns the zero address.
Notes
For backwards-compatibility, implementations MUST emit the standard ERC-20 Transfer
event when a transfer occurs, regardless of whether the sender and recipient are addresses or ERC-721 tokens. In the case that either sender or recipient are tokens, the corresponding parameter in the Transfer
event SHOULD be the contract address of the token.
Implementations MUST implement all ERC-20 and ERC-223
functions in addition to the functions specified in this interface.
Rationale
Two different kinds of composable (top-down and bottom-up) exist to handle different use cases. A regular ERC-721 token cannot own a top-down composable, but it can own a bottom-up composable. A bottom-up composable cannot own a regular ERC-721 but a top-down composable can own a regular ERC-721 token. Having multiple kinds of composables enable different token ownership possibilities.
Which Kind of Composable To Use?
If you want to transfer regular ERC-721 tokens to non-fungible tokens, then use top-down composables.
If you want to transfer non-fungible tokens to regular ERC-721 tokens then use bottom-up composables.
Explicit Transfer Parameters
Every ERC-998 transfer function includes explicit parameters to specify the prior owner and the new owner of a token. Explicitly providing from and to is done intentionally to avoid situations where tokens are transferred in unintended ways.
Here is an example of what could occur if from was not explicitly provided in transfer functions:
An exchange contract is an approved operator in a specific composable contract for user A, user B and user C.
User A transfers token 1 to user B. At the same time the exchange contract transfers token 1 to user C (with the implicit intention to transfer from user A). User B gets token 1 for a minute before it gets incorrectly transferred to user C. The second transfer should have failed but it didn't because no explicit from was provided to ensure that token 1 came from user A.
Backwards Compatibility
Composables are designed to work with ERC-721, ERC-223
and ERC-20 tokens.
Some older ERC-721 contracts do not have a safeTransferFrom
function. The getChild
function can still be used to transfer a token to an ERC-721 top-down composable.
If an ERC-20 contract does not have the ERC-223
function transfer(address _to, uint _value, bytes _data)
then the getERC20
function can still be used to transfer ERC-20 tokens to an ERC-20 top-down composable.
Reference Implementation
An implementation can be found here: https://github.com/mattlockyer/composables-998
Security Considerations
Needs discussion.
Copyright
Copyright and related rights waived via CC0.