EIP-4987 "held" token standard (NFTs + DeFi)

Sorry for deleting the reply.
After submitting it I figured out I didn’t understand you correctly. Now I understand it much better :slight_smile:

Basically the owner of the token would be a contract address where the token is being held, and that contract will hold the information regarding the true owner. Is that right?
I understand how the use case I described can be supported.
I was trying to think of a way of switching the owner in the ERC721 contract to point to the true owner but with limiting the transfer rights the owner has over the token. However I guess it is not possible as the transferFrom function will always accepts the transfer if the registered owner is the msg.sender.

Just to be sure, regarding your proposal, existing ERC721 contracts wouldn’t have to make any updates. Only 3rd parties looking for a true owner (in case of a contract owner) will need this “overhead”. And any 3rd party who wants to allow some DeFi or lending mechanisms, which will support the proposed feature, will need to implement the current ERC proposal. Is that correct?

So how can we advance this proposal? :face_with_monocle:

No problem, glad it is making sense now.

Right, according to the original ERC721 contract, the ownerOf() will be the smart contract holding the token.

Exactly you are right on. This is designed to work with any existing ERC721. The overhead of adoption here is on DeFi mechanisms to implement the proposed standard interface and on third parties to recognize that standard.

Still collecting some more feedback, then will be opening up the actual EIP

1 Like

I think this is the better interface (the one with functionalOwnerOf).
This would then work for ERC-1155. Maybe then submit two interfaces or rename it so that it applies to both?

To support ERC1155 (as well as ERC721) the interface should be modified.
The issue with ERC1155 is that (I think) it is a problem to look for the owner of a token ID, as there could be multiple owners in case that the token ID points to a fungible token.

I think it might make sense to retrieve the multiple possible owners, and to follow with a query of the balance of a specific owner.

Anyway I would suggest the following moidification for ERC1155 support:

interface ERCTokenHold {

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

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

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

      // returns the functional balance of an owner for a specific token ID
    function functionalBalanceOf(address _tokenAddress, address _owner, uint256 _tokenID) external view returns (uint256);
}

In case of an ERC721 contract, the value can be ignored (and no use for the functionalBalanceOf function).
I guess this would also support ERC20? Ignoring the token ID.

Yep, planning to submit two different interfaces for ERC721 and ERC1155

This could definitely work, but I think it feels cleaner to use explicit independent interfaces for each token type. This will also allow ERC165 responses to be more descriptive

1 Like

@devinaconley I think that the proposal that you are describing addresses the same need as the EIP-4400 that we submitted recently. I think that it would be interesting for you to check it out and get your feedback. I am curious to know whether the proposed 4400 standard will address your needs :slight_smile:

Personally, I think that the overriding of the owner method is a dangerous direction, so moving to functionalOwnerOf is the right step.

I am excited to see that other people are recognising the need for such a standard as it would enable NFT lending/renting/staking!

Hey @Daniel-K-Ivanov - thanks for sharing that proposal. Likewise, glad to see other folks are looking seriously at this kind of standard.

EIP4400 (ERC721Consumer) definitely seems to be in the same spirit! But I think there are a couple key differences in the approach:

  • this proposal puts the burden of reporting on the holding contract “owner”, where ERC721Consumer puts that burden on the token contract
  • ERC721Consumer requires an upgrade for existing ERC721 tokens
  • the bookkeeping for ERC721Consumer will likely have higher gas costs, as that is another dictionary to be managed during transfer, staking, sales, etc.
  • the ERC721Consumer approach is probably more flexible for EOA usage without a smart contract

Agreed on this :+1:


Overall, good to see another take on this problem. Definite pros and cons to each approach

1 Like

I agree that this is very different from EIP-4400 as these have a very large impact on the functionality:

  • this proposal puts the burden of reporting on the holding contract “owner”, where ERC721Consumer puts that burden on the token contract
  • ERC721Consumer requires an upgrade for existing ERC721 tokens

I was playing around with a variant of this with time-based ‘hold’, while wrapping the ‘hold/lend’ as a ERC721 itself. I think that the functionality (payment/lending) that I’m looking for can be built on top of what you’re proposing. I’m using virtualOwnerOf to make it distinct from ownerOf function.

Let me know if you’ll like some examples done or if you’re looking for help to write/push this forward!

Hey @flaskr - thanks for the feedback on this. Great to hear that this proposed interface would fit with your use case and need for an associated standard.

Appreciate that! I am actually working on the EIP draft and a code example now, which should be wrapped up this weekend. Would be great to include another example from your side as well.

I’ll keep you posted here.

Would be great to get some thoughts on naming convention here. The idea is that all interface functions will be named with this prefix (e.g. in the ERC1155 case, heldOwnerOf and heldBalanceOf)

What function prefix(es) do you prefer?

  • heldOwnerOf
  • tokenOwnerOf
  • functionalOwnerOf
  • virtualOwnerOf
  • delegateOwnerOf

0 voters

I do think that ‘heldOwnerOf’ isn’t very readable as a sentence. functionalOwner has my vote. I just added support for EIP-165 and “cross-ERC721Hold” owner queries to my repo.

On interface between ownerOf(id) and ownerOf(address, id)

Personally, I prefer the interface to be specific to a single ERC-721. This is because I would like the option to implement the ‘functional ownership’ as an ERC-721 itself - being able to transfer/sell your lease can be very powerful. It’s possible to add in the ownerOf(address, id) function to an ERC-721 wrapper but it looks pretty redundant. Having to support the extra address calldata might cost extra gas too.

I also envision a separate contract or interface that allows people to check ownerOf(address, id). Why not have 2 interfaces? A contract could implement both if it wants to, but it keeps one from polluting the other.

eg.
ERC721Hold

  • ownerOf(id)

ERC721ManyHold

  • ownerOf(tokenAddress, id)

I would also like one for ERC1155 but the scope might be too big.

1 Like

I actually mentioned the possibility of reusing the ownerOf method for a tokenized position in the initial post. The consensus was that overriding this method from two different interfaces was a little hacky/dangerous

One challenge with using separate interfaces is that a calling method would need to check two different interface identifiers to look up functional ownership

1 Like

Just wrapped up an example implementation of the proposed standard here

1 Like

And submitted the proposal draft for this held token standard here

1 Like

Looking good so far, and apologies if this has already been covered, but if a holder contract holds both an ERC-20 and an ERC-721, what happens?

Would it possibly make more sense to just have a single function per operation (ex. heldBalanceOf(address owner, address token, uint256 tokenId)) where tokenId is always 0x0 for ERC-20s?

Thanks for the review @SamWilsn ! Addressing your feedback and suggestions now.

The thinking behind separate interfaces for each token type is that any consumer logic will usually only want to query held token info on a specific token type at a time.

In the case of a contract holding both ERC-20 and ERC-721, the heldBalanceOf(address owner, address token) is actually the same signature.

If a contract is also holding ERC-1155, the function could be overloaded with heldBalanceOf(address owner, address token, uint256 tokenId). The consumer would hit the appropriate signature depending on the interface of interest.

@SamWilsn your feedback on events brings up another good question. I think there’s a case to be made that this interface should not require any events to be emitted.

One of the main goals is for this interface to be extremely lightweight and non-intrusive. Additional events/gas doesn’t help with that

Plus, we could arguably get all the equivalent data by indexing underlying token Transfer events and filtering where to or from match our holder of interest

Agree… its good idea… very useful…

There are apps where users can deposit their NFTs…
But then its not visible in their wallet and marketplaces…

If this standard being adopted… then user can deposit his NFT to another contract, but it still remains visible in his wallet…

Particularly in our case, we want to allow users prove ownership of their NFTs… including those NFTs already staked into some contracts…

If this standard being supported by staking contract… then we can read heldOwnerOf to find actual owner… also we can listen Hold and Release events… to deterministically reduce database… I think events are important…

1 Like

and events are not so bad… they are not being saved into blockchain… I mean they are much cheaper than storage data in smart contract

1 Like

Thanks for the thoughts here @7flash, glad to hear this would be applicable for your use case