ERC standard for "held" Non-fungible token (NFTs + DeFi)

Hi all,

As NFTs and DeFi start to converge, there will more commonly be a distinction between the actual owner of an ERC721 token and the functional owner/user.

Examples include:

  • staked NFTs
  • lending protocols that accept NFTs as collateral
  • fractionalized NFTs

Currently, usage in one of those DeFi mechanisms would conflict with ownership verification for gaming, PFPs, art gallery showcases, etc.

Want to start up a thread on a standard ERC interface that could be used to very easily check the “functional owner” of an NFT held by another smart contract. Hopefully, something like this already exists that I am not aware of.

But if not…

I would propose something very lightweight that could be implemented by contracts with little overhead.

interface ERC721Hold {

    // emitted when the token is transferred to the contract
    event Hold(address indexed _from, uint256 indexed _tokenId);

    // emitted when the token is released back to the user
    event Release(address indexed _to, uint256 indexed _tokenId);

    // returns the functional owner of the held token
    function ownerOf(uint256 _tokenID) external view returns (address);

    // returns the address to the underlying held ERC721 asset
    function asset() external view returns (address);
}

Note: this would also implement ERC165 so applications could easily check a contract for this interface.

Note: the method ownerOf was intentionally reused from ERC721 so that contracts which fully wrap and tokenize a held NFT position can implement both ERC721 and ERC721Hold without additional overhead.

Here is some example logic to check for the NFT owner while respecting the ERC721Hold interface

library {
    function getOwner(address addr, uint256 id) public pure returns (address) {
        IERC721 token = IERC721(addr);
        address owner = token.ownerOf(id);
        if (owner.isContract()) {
            try IERC165(token_).supportsInterface(0x00000000) returns (bool ret) {
                if (ret && IERC721Hold(owner).asset() == addr) {
                    return IERC721Hold(owner).ownerOf(id);
                }
            } catch {
                return owner;
            }
        }
        return owner;
    }
}

Really appreciate any thoughts or feedback!

Thanks all,
Devin

5 Likes

Another aspect to consider is that contracts could potentially hold tokens from more than one NFT collection. In this case, the above interface would be too limiting.

To further generalize, something like this could used

interface ERC721Hold {

    // emitted when the token is transferred to the contract
    event Hold(address indexed _user, address indexed _tokenAddress, uint256 indexed _tokenId);

    // emitted when the token is released back to the user
    event Release(address indexed _user, address indexed _tokenAddress, uint256 indexed _tokenId);

    // returns the functional owner of the held token
    function functionalOwnerOf(address _tokenAddress, uint256 _tokenID) external view returns (address);
}

Twitter discussion for reference: