Skip to content

Latest commit

 

History

History
397 lines (335 loc) · 12.5 KB

eip-4786.md

File metadata and controls

397 lines (335 loc) · 12.5 KB
eip title description author discussions-to status type category created requires
4786
Link Common Token to ERC-721
Link ERC-721 token with ERC-721/ERC-1155/ERC-20 token
Poria Cattus (@poria-cat)
Draft
Standards Track
ERC
2022-02-09
165

Abstract

ERC-4786 provides an extension for ERC-721 to be composed with other tokens (ERC-721/ERC-1155/ERC-20). This applies to the creation of a composable/graph NFT. In this standard, the ERC-721 Token is a first class citizen and can freely compose with other Tokens. The result is get an interesting NFT with a graph structure.

Motivation

The ability to compose opens up many more possibilities for ERC-721, here are some possible scenarios:

  1. Assets owned by an address can be transformed into assets owned by an NFT.
  2. Linking an avatar NFT to an ENS
  3. An avatar/game character composed from different NFTs
  4. A number of assets packaged with an NFT and sold together, etc.

ERC-998 attempts to solve this problem, but ERC-998 introduces more complexity to the composability, and the main advantages of this standard over ERC-998 are:

  1. There is no top-down/bottom-up concept
  2. The function names are closer to those of the regular operations graph structure (link/updateTarget/unlink), which makes the standard easier to understand
  3. The function names are sufficiently uniform when dealing with different Tokens (linkERCx/updateERCxTarget/unlinkERCx)
  4. Easy to extend

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Every ERC-4786 compliant contract must implement the ERC165 interfaces

/**
 * @title ERC-4786: Link Common Token to ERC-721
 * @dev See https://eips.ethereum.org/EIPS/eip-4786
 */
interface ERC4786 {
    // Used to represent an ERC-721 Token
    struct ERC721Token {
        address tokenAddress;
        uint256 tokenId;
    }
    /**
     * @dev Emited when sourceToken linked to targetToken.
     * @param from who link `sourceToken` to `targetToken`
     * @param sourceToken starting node, child node
     * @param targetToken ending node, parent node
     * @param data Additional data when link to the targetToken, either the order of the NFT or other information
     */
    event Linked(
        address from,
        ERC721Token sourceToken,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev Emited when sourceToken change target nft to targetToken.
     */
    event TargetUpdated(
        ERC721Token sourceToken,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev Emited when sourceToken unlinked to `to`.
     * @param to unlink sourceToken to one's address
     */
    event Unlinked(address to, ERC721Token sourceToken, bytes data);

    /**
     * @dev find a ERC-721 token's root NFT
     * @param token a ERC-721 token
     */
    function findRootToken(ERC721Token memory token)
        external
        view
        returns (address rootTokenAddress, uint256 rootTokenId);

    /**
     * @dev get a ERC-721 token's target token
     * @param sourceToken  a ERC-721 token
     */
    function getTarget(ERC721Token memory sourceToken)
        external
        view
        returns (address tokenAddress, uint256 tokenId);

    /**
     * @dev for compatibility with balanceOfERC1155, balanceOfERC20.
     * if an ERC-721 token is not linked to a targetToken, then balance is 0, otherwise it is 1
     * @param targetToken linked ERC-721 token
     * @param erc721Token a ERC-721 token
     */
    function balanceOfERC721(
        ERC721Token memory targetToken,
        ERC721Token memory erc721Token
    ) external view returns (uint256);
    
    /**
     * @dev let contract can receive ERC-721 token
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev link a ERC-721 token to another ERC-721 token
     * @param sourceToken link this token to another
     * @param targetToken linked token
     * @param data can as the order of linking to NFT or other information
     */
    function link(
        ERC721Token memory sourceToken,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;

    /**
     * @dev update token's target token
     * @param sourceToken change this token's target token
     * @param targetToken update target token to this token
     * @param data an as the order of NFT or other information
     */
    function updateTarget(
        ERC721Token memory sourceToken,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;

    /**
     * @dev unlink a ERC-721 token to a address
     * @param to unlink token to this address
     * @param sourceToken unlink this token to `to`
     * @param data can as information about token changes or other information
     */
    function unlink(
        address to,
        ERC721Token memory sourceToken,
        bytes memory data
    ) external;
}

interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

The Compose ERC20 extension is OPTIONAL for ERC-4786 smart contracts. This allows your smart contract compose ERC-20 token with ERC-721 token.

interface ERC4786WithERC20 {
    /**
     * @dev Emited when ERC-20 token linked to target token
     */
    event ERC20Linked(
        address from,
        address erc20Address,
        uint256 amount,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev Emited when update ERC-20 token's target token
     */
    event ERC20TargetUpdated(
        address erc20Address,
        uint256 amount,
        ERC721Token sourceToken,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev Emited when unlink ERC-20 token to `to`
     */
    event ERC20Unlinked(
        address to,
        address erc20Address,
        uint256 amount,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev get the balance of ERC-20 token linked to the target token
     */
    function balanceOfERC20(
        ERC721Token memory targetToken,
        address erc20Address
    ) external view returns (uint256 balance);

    /**
     * @dev link ERC-20 token to target token
     */
    function linkERC20(
        address erc20Address,
        uint256 amount,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;

    /**
     * @dev  update ERC-20 token's target token
     */
    function updateERC20Target(
        address erc20Address,
        uint256 amount,
        ERC721Token memory sourceToken,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;

    /**
     * @dev unlink ERC-20 token to `to`
     */
    function unlinkERC20(
        address to,
        address erc20Address,
        uint256 amount,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;
}

The Compose ERC1155 extension is OPTIONAL for ERC-4786 smart contracts. This allows your smart contract compose ERC-1155 token with ERC-721 token.

interface ERC4786WithERC1155 {
    // as ERC-1155 token
    struct ERC1155Token {
        address tokenAddress;
        uint256 tokenId;
    }

    /**
     * @dev Emited when link a ERC-1155 token to ERC-721 token
     */
    event ERC1155Linked(
        address from,
        ERC1155Token erc1155Token,
        uint256 amount,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev Emited when ERC-1155 token's target token updated
     */
    event ERC1155TargetUpdated(
        ERC1155Token erc1155Token,
        uint256 amount,
        ERC721Token sourceToken,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev Emited when ERC-1155 token unlinked to `to`
     */
    event ERC1155Unlinked(
        address to,
        ERC1155Token erc1155Token,
        uint256 amount,
        ERC721Token targetToken,
        bytes data
    );

    /**
     * @dev get the balance of ERC-1155 token linked to the target token
     */
    function balanceOfERC1155(
        ERC721Token memory targetToken,
        ERC1155Token memory erc1155Token
    ) external view returns (uint256 balance);
    
    /**
     * @dev let contract can receive ERC-1155 token
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev link ERC-1155 token to target token
     * @param erc1155Token link this token to targetToken
     * @param amount ERC-1155 token's amount
     * @param targetToken link to this token
     * @param data information on token changes or other information.
     */
    function linkERC1155(
        ERC1155Token memory erc1155Token,
        uint256 amount,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;

    /**
     * @dev update ERC-1155 token's target token
     */
    function updateERC1155Target(
        ERC1155Token memory erc1155Token,
        uint256 amount,
        ERC721Token memory sourceToken,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;

    /**
     * @dev unlink ERC-1155 token to `to`
     */
    function unlinkERC1155(
        address to,
        ERC1155Token memory erc1155Token,
        uint256 amount,
        ERC721Token memory targetToken,
        bytes memory data
    ) external;
}

Rationale

The contract records the relationship between the source token and the target token, where the relationship between the source token and the target token is one-to-many.

The relationship between the target token and the source token is one-to-many, and the relationship is updated when a link/unlink/updateTarget operation occurs.

Root Token:

Each ERC-721 token linked to other ERC-721 token has a root token, and the owner of the root token is the owner of the entire graph. For a ERC-721 token, it's root token can be found through findRootToken.

Inside findRootToken is done by constantly searching for a target token. When a token does not have a target token but has source tokens, it is a root token.

ERC-20/ERC-1155:

For ERC-20 token/ERC-1155 token, both have fungible attributes, so need to link them to a ERC-721Token.

ERC-777 and other fungible tokens:

While most tokens are using ERC-20, there are some tokens that use ERC-777 or other fungible token standard, for these niche tokens you can use function names like linkERC777/unlinkERC777/updateERC777Target

Compose ERC-4786 with ERC-721 contracts:

If the contract code for ERC-4786 and ERC-721 is placed directly in one contract, this may cause the contract to exceed the size limit, so it is better to deploy it in two separate contracts. If you want to use a concept similar to the container NFT token, you can limit the targetToken of the link to only the NFTs minted in a particular contract and check the target token of the updateTarget.

Backwards Compatibility

The ERC-4786 is an extension of the ERC-721 and does not modify the interface of ERC-721, so it is 100% compatible with the ERC-721.

References

Standards

Implementations

Security Consideration

There are no security considerations related directly to the implementation of this standard.

Citation

Please cite this document as:

Poria Cattus, "EIP-4786: Link Common Token to ERC-721," Ethereum Improvement Proposals, no. 4786, February 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4786.