ERC-7631 Dual-nature token pair (Dual Nature Token Pair)
Final · NFTs & multi-token · created 2024-02-21
Defines how to link an ERC-20 token and an ERC-721 token (NFT) as a pair, so that a transfer on one side is reflected on the other, along with a lookup for the link between the two contracts and a setting to skip automatic NFT minting.
Why it is needed
There is a design in which a single token is used in two forms at once: ERC-20 (fungible token) and ERC-721 (non-fungible token, NFT). For example, acquiring ERC-20 automatically mints NFTs in proportion to the holding, which makes fractional ownership of NFTs possible using only standard tokens. But without a standard way to verify from outside that two contracts are linked, decentralized exchanges and NFT marketplaces cannot show the relationship between the two tokens on screen. Also, if NFTs are automatically minted or transferred every time ERC-20 moves, accounts that handle large amounts, such as vesting contracts or liquidity pools, must pay O(n) gas proportional to the quantity, and the transfer itself can fail by exceeding the block gas limit. This standard solves these problems by defining a common interface for looking up the link and a way to configure skipping NFT minting.
What to implement
function mirrorERC721() external view returns (address);: Returns the address of the paired mirror ERC-721 contract. If the link is not yet established, it may revert or return the zero address.event SkipNFTSet(address indexed owner, bool status);: An event that tells indexers that the NFT skip state of owner has changed to status.function getSkipNFT(address owner) external view returns (bool);: Returns true if NFT minting and transfers for owner should be skipped when synchronizing from ERC-20 to ERC-721.function setSkipNFT(bool status) external;: Sets the NFT skip state of the calling account itself and emits the SkipNFTSet event.function baseERC20() external view returns (address);: Returns the address of the paired base ERC-20 contract. If the link is not yet established, it may revert or return the zero address.
In depth
A pair made of a base and a mirror
A dual-nature token pair consists of one ERC-20 contract and one ERC-721 contract. By convention, the ERC-20 side is called the base contract and the ERC-721 side is called the mirror contract. In typical implementations the ERC-20 is used as the base because the ERC-721 holding count can be derived easily from the ERC-20 balance, but placing most of the logic on the ERC-721 side is not prohibited if needed. Each token fully complies with the ERC-20 and ERC-721 standards respectively, while a transfer on one side can be reflected on the other. This enables applications such as native fractional ownership, where acquiring ERC-20 leads to automatic minting of ERC-721 tokens in proportion to the holding.
Cross-reference rules and immutability
The base must implement mirrorERC721(), and the mirror must implement baseERC20(). To indicate that they are not yet linked, these two methods may revert or return the zero address. When they return a nonzero address, that address must implement the interface on the other side, and the method there must in turn return this contract's address. Once a method has returned a nonzero address, it must not revert afterward and its value must not change. Returning a nonzero address is itself the signal that the required interface is implemented, so no separate detection mechanism such as ERC-165 is needed.
Behavior of getSkipNFT and setSkipNFT
If getSkipNFT(owner) returns true, ERC-20 to ERC-721 synchronization must skip minting and transferring ERC-721 for that owner. This method may revert when it is uninitialized or the feature is unsupported. In that case, interacting code must treat the contract as having no setSkipNFT feature, hide the related UI, and regard the state of that owner as undefined. However, once the method has returned true or false for an owner, it must not revert for that owner again. setSkipNFT sets the caller's own state and emits a SkipNFTSet event, and it is recommended to leave it callable by anyone. To simplify implementations, the event may be emitted even when the state does not change. Also, because contracts compiled with Solidity or Vyper revert on calls to undefined methods by default, a base implementation that does not explicitly define these two methods is regarded as conforming to IERC7631BaseNFTSkippable.
When NFT skipping is needed
The skip setting lets an account avoid automatic ERC-721 minting on every ERC-20 transfer. The source gives these examples: putting a large amount of ERC-20 into a vesting contract that will distribute it to many users, putting a large amount of ERC-20 into a candy machine contract that sells ERC-721, adding and removing large amounts of ERC-20 in a liquidity pool, and moving large amounts between admin accounts. Putting this method in the standard lets apps easily show users a skip option and lets any amount of ERC-20 be moved without the O(n) gas cost of minting multiple ERC-721 tokens. Bulk automatic transfers can also exceed the block gas limit, so skipping is recommended even on EVM chains with cheap gas. The source describes a pattern where getSkipNFT returns true by default when the owner is a smart contract as useful. The return value is a boolean for simplicity, and developers can add methods if more complex behavior is needed.
Synchronization logic and linking procedure are left to implementers
The standard does not define the synchronization logic between the two tokens itself. This leaves flexibility for new use cases such as auto-rebasing tokens and for different implementation patterns. Any linking procedure is also allowed, such as linking in the constructor, in an initialization function, or through an admin-only public method. There is only one constraint: once a pair is established, it must not be changeable, which keeps the indexing logic simple. This proposal is a standard finalized with Final status.
Things to watch out for
- Access control for synchronization functions: External methods for the synchronization logic must be protected so that only the paired counterpart contract can call them.
- Sniping rare NFTs: A collection that offers ERC-721 tokens of differing rarity must reveal its metadata in a way that makes it hard to cherry-pick rare NFTs by scraping metadata and making ERC-20 transfers. Revealing metadata only after the same account has held for a certain period is recommended.
- Denial of service from gas exhaustion: If an ERC-20 transfer automatically triggers minting, transferring, and burning of multiple ERC-721 tokens, it costs O(n) gas instead of the usual O(1), and the logic that selects ERC-721 token IDs can also use extra gas. The synchronization logic must account for these gas costs and prevent transfers from being blocked by gas exhaustion.