ERC-6220 Equippable Composable Parts NFT (Composable NFTs utilizing Equippable Parts)
Final · NFTs & multi-token · created 2022-12-20
An ERC-721 extension standard that lets an NFT select and combine parts predefined in a catalog, and equip or unequip other NFTs in slots. Equipping does not create a new token, and the equipped items are rendered together when the token is queried.
Why it is needed
Previously, combining several NFTs into one composite required either gathering all compatible NFTs in an account and using them indiscriminately, or building a separate ledger in a smart contract or an off-chain database. When more than one NFT was eligible for the same slot, it was also unpredictable which one would be applied. In addition, when a game character grew and obtained items, each item had to be carried around as a separate NFT, and a central operator had to manage the equipped state. Scarcity was a problem as well. The same hat NFT could be worn by 500 avatars in 500 game instances at the same time, because the visual representation was only client-side processing. This standard creates a framework in which a single NFT directly chooses which parts to use as part of the whole and keeps that information on-chain, providing predictable composition and real digital scarcity.
What to implement
event SetEquippables(uint64 indexed partId, address[] equippableAddresses);: An event signaling that the list of addresses allowed to equip a part has been overwritten with a new list.event SetEquippableToAll(uint64 indexed partId);: An event signaling that the part has been marked as equippable by all addresses.function getMetadataURI() external view returns (string memory);: Returns the catalog's base metadata URI.function getType() external view returns (string memory);: Returns the catalog's itemType.function checkIsEquippable(uint64 partId, address targetAddress) external view returns (bool);: Checks whether the collection at the given address can be equipped to the part.function checkIsEquippableToAll(uint64 partId) external view returns (bool);: Checks whether the part can be equipped by all addresses.function getPart(uint64 partId) external view returns (Part memory);: Looks up a single Part struct by part ID.function getParts(uint64[] calldata partIds) external view returns (Part[] memory);: Looks up, in one call, the array of Part structs for several part IDs.
In depth
Two kinds of parts: fixed and slot
A Fixed part is a complete component with its own metadata. It does not change during the NFT's lifetime and cannot be replaced. Once defined, it can be equipped by many tokens across many collections, which distributes recurring assets efficiently. A Slot part has no metadata of its own and allows a selected NFT collection to be equipped into it. Because the equipped token supplies its own metadata, it becomes swappable content controlled by the owner. You do not need to use both kinds, and you can apply only the configuration you need.
Catalog structure and the Part struct
A catalog is a collection of fixed and slot parts, and it is not tied to a single collection, so it can support multiple NFT collections. The Part struct consists of itemType (None, Slot, Fixed), z (uint8) which sets the rendering depth, equippable, an array of addresses allowed to equip, and metadataURI. Parts are added by specifying partId through IntakeStruct, and using a partId that is already in use reverts the addition. One catalog entry needs at least 3 storage slots (about 60,000 gas as of the Berlin hard fork), and considering the length of an IPFS URI, 5 to 7 slots is realistic. It costs 25,000,000 to 35,000,000 gas per 250 assets, while the maximum block size on mainnet is 30M.
equip, unequip, and the Equipment storage structure
equip takes an IntakeEquip struct (tokenId, childIndex, assetId, slotPartId, childAssetId) as input. Equipping does not create a new token. It only adds a component that will be rendered together when the token is queried. unequip can be called only by the token owner or an account the owner has granted management permission. The equipped state is stored as an Equipment struct (assetId, childAssetId, childId, childEquippableAddress) and is queried per slot with getEquipment. Each action is announced with the ChildAssetEquipped and ChildAssetUnequipped events.
Equip permission verification system
What can be equipped where is verified at several layers. On the catalog side, checkIsEquippable(partId, targetAddress) checks whether a specific collection can be equipped to a part, and checkIsEquippableToAll(partId) checks whether a part is open to all addresses. On the token side, an asset belongs to an equip group called equippableGroupId, and as the ValidParentEquippableGroupIdSet event announces, whether a group can be equipped is specified per group for a specific slot of a specific parent collection. canTokenBeEquippedWithAssetIntoSlot lets you query whether equipping is possible before you equip. If NFTs were equipped directly without any oversight, the resulting combinations would be unpredictable, so the design intent is to verify parts in advance through the catalog so that they combine as intended.
Provable digital scarcity
With existing NFTs, supply was limited but usage was not. With the same wallet and the same NFT, you can join 500 game instances and put the same hat on 500 avatars at once, because the visual representation is only client-side processing. This standard enforces that once a hat is equipped to one avatar (equipped after being transferred into the avatar token), it cannot be equipped to another avatar. isChildEquipped is used to block transfer of an equipped child, which supports this guarantee.
Dependent standards and compatibility
This standard requires ERC-165, ERC-721, ERC-5773, and ERC-6059. The IERC6220 interface inherits IERC5773, its ERC-165 identifier is 0x28bc9ae4, and the identifier of ICatalog is 0xd912401f. It is designed to be compatible with ERC-721, so existing ERC-721 tools and infrastructure can be used as is. A method that fetches all equipped parts at once was not included because not every implementer needs it, and it can be added through an extension or hook, or replaced by an indexer. Its status is Final, a finalized standard.
Things to watch out for
- Beware of hidden logic: The same security considerations as ERC-721 apply. Any function, including burn, adding resources, and accepting resources, may contain hidden logic, so be careful when dealing with contracts that have not been audited.